<!DOCTYPE html>
<html lang="en-US" dir="ltr">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Spring事务 | VitePress</title>
    <meta name="description" content="A VitePress site">
    <link rel="preload stylesheet" href="/notebook/assets/style.3dbfd0c2.css" as="style">
    
    <script type="module" src="/notebook/assets/app.8aaa4cbe.js"></script>
    <link rel="preload" href="/notebook/assets/inter-roman-latin.2ed14f66.woff2" as="font" type="font/woff2" crossorigin="">
    <link rel="modulepreload" href="/notebook/assets/chunks/framework.1336c4e5.js">
    <link rel="modulepreload" href="/notebook/assets/chunks/theme.20cddc0c.js">
    <link rel="modulepreload" href="/notebook/assets/SSM_Spring.md.ab514659.lean.js">
    <script id="check-dark-light">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
  </head>
  <body>
    <div id="app"><div class="Layout" data-v-255ec12d><!--[--><!--]--><!--[--><span tabindex="-1" data-v-ae3e3f51></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-ae3e3f51> Skip to content </a><!--]--><!----><header class="VPNav" data-v-255ec12d data-v-7e5bc4a5><div class="VPNavBar has-sidebar" data-v-7e5bc4a5 data-v-0937f67c><div class="container" data-v-0937f67c><div class="title" data-v-0937f67c><div class="VPNavBarTitle has-sidebar" data-v-0937f67c data-v-86d1bed8><a class="title" href="/notebook/" data-v-86d1bed8><!--[--><!--]--><!--[--><img class="VPImage logo" src="/notebook/Vue.png" alt data-v-8426fc1a><!--]--><!--[-->任硕的文档<!--]--><!--[--><!--]--></a></div></div><div class="content" data-v-0937f67c><div class="curtain" data-v-0937f67c></div><div class="content-body" data-v-0937f67c><!--[--><!--]--><div class="VPNavBarSearch search" style="--vp-meta-key:&#39;Meta&#39;;" data-v-0937f67c><!--[--><!----><div id="docsearch"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><svg class="DocSearch-Search-Icon" width="20" height="20" viewBox="0 0 20 20" aria-label="search icon"><path d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z" stroke="currentColor" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"><kbd class="DocSearch-Button-Key"></kbd><kbd class="DocSearch-Button-Key">K</kbd></span></button></div><!--]--></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-0937f67c data-v-7f418b0f><span id="main-nav-aria-label" class="visually-hidden" data-v-7f418b0f>Main Navigation</span><!--[--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>Java学前端</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/HTML+JS.html" data-v-2f2cfafc><!--[-->HTML+JS<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/CSS.html" data-v-2f2cfafc><!--[-->CSS<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/Vue2+%E7%BB%84%E4%BB%B6.html" data-v-2f2cfafc><!--[-->Vue2+组件<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/Vue3+%E7%BB%84%E4%BB%B6.html" data-v-2f2cfafc><!--[-->Vue3+组件<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/React.html" data-v-2f2cfafc><!--[-->React<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>软件测试</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95/%E6%B5%8B%E8%AF%95%E5%9F%BA%E7%A1%80.html" data-v-2f2cfafc><!--[-->测试基础<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95/%E5%8E%8B%E5%8A%9B%E6%B5%8B%E8%AF%95.html" data-v-2f2cfafc><!--[-->压力测试<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>多线程</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E5%B9%B6%E5%8F%91%20&amp;%20%E5%A4%9A%E7%BA%BF%E7%A8%8B/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-2f2cfafc><!--[-->基础篇<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E5%B9%B6%E5%8F%91%20&amp;%20%E5%A4%9A%E7%BA%BF%E7%A8%8B/%E5%B9%B6%E5%8F%91%E5%AE%8C%E5%96%84.html" data-v-2f2cfafc><!--[-->进阶篇<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>开发工具</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/IDEA/Chrome.html" data-v-2f2cfafc><!--[-->Chrome<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/IDEA/IDEA%E5%9F%BA%E7%A1%80.html" data-v-2f2cfafc><!--[-->IDEA基础<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/IDEA/IDEA%E6%8F%92%E4%BB%B6.html" data-v-2f2cfafc><!--[-->IDEA插件<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/IDEA/VS%20Code.html" data-v-2f2cfafc><!--[-->VS Code<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>消息中间件</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ.html" data-v-2f2cfafc><!--[-->RabbitMQ<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/RocketMQ.html" data-v-2f2cfafc><!--[-->RocketMQ<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/Kafka.html" data-v-2f2cfafc><!--[-->Kafka<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/Canal.html" data-v-2f2cfafc><!--[-->Canal<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-0937f67c data-v-f6a63727><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title="toggle dark mode" aria-checked="false" data-v-f6a63727 data-v-82b282f1 data-v-f3c41672><span class="check" data-v-f3c41672><span class="icon" data-v-f3c41672><!--[--><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="sun" data-v-82b282f1><path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"></path><path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"></path><path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"></path><path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"></path><path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"></path><path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"></path><path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"></path><path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"></path><path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="moon" data-v-82b282f1><path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"></path></svg><!--]--></span></span></button></div><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-0937f67c data-v-0394ad82 data-v-7bc22406><!--[--><a class="VPSocialLink no-icon" href="https://github.com/renshuo123/renshuo123.github.io" aria-label="github" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></a><a class="VPSocialLink no-icon" href="#" aria-label="twitter" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Twitter</title><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg></a><a class="VPSocialLink no-icon" href="https://github.com/" aria-label target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg t="1676028692954" class="icon" ...</path></svg></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-0937f67c data-v-40855f84 data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-a7b5672a><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="icon" data-v-a7b5672a><circle cx="12" cy="12" r="2"></circle><circle cx="19" cy="12" r="2"></circle><circle cx="5" cy="12" r="2"></circle></svg></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><!----><!--[--><!--[--><!----><div class="group" data-v-40855f84><div class="item appearance" data-v-40855f84><p class="label" data-v-40855f84>Appearance</p><div class="appearance-action" data-v-40855f84><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title="toggle dark mode" aria-checked="false" data-v-40855f84 data-v-82b282f1 data-v-f3c41672><span class="check" data-v-f3c41672><span class="icon" data-v-f3c41672><!--[--><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="sun" data-v-82b282f1><path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"></path><path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"></path><path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"></path><path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"></path><path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"></path><path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"></path><path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"></path><path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"></path><path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="moon" data-v-82b282f1><path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"></path></svg><!--]--></span></span></button></div></div></div><div class="group" data-v-40855f84><div class="item social-links" data-v-40855f84><div class="VPSocialLinks social-links-list" data-v-40855f84 data-v-7bc22406><!--[--><a class="VPSocialLink no-icon" href="https://github.com/renshuo123/renshuo123.github.io" aria-label="github" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></a><a class="VPSocialLink no-icon" href="#" aria-label="twitter" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Twitter</title><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg></a><a class="VPSocialLink no-icon" href="https://github.com/" aria-label target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg t="1676028692954" class="icon" ...</path></svg></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-0937f67c data-v-e5dd9c1c><span class="container" data-v-e5dd9c1c><span class="top" data-v-e5dd9c1c></span><span class="middle" data-v-e5dd9c1c></span><span class="bottom" data-v-e5dd9c1c></span></span></button></div></div></div></div><!----></header><div class="VPLocalNav reached-top" data-v-255ec12d data-v-5cfd5582><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-5cfd5582><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="menu-icon" data-v-5cfd5582><path d="M17,11H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,11,17,11z"></path><path d="M21,7H3C2.4,7,2,6.6,2,6s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,7,21,7z"></path><path d="M21,15H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,15,21,15z"></path><path d="M17,19H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,19,17,19z"></path></svg><span class="menu-text" data-v-5cfd5582>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-5cfd5582 data-v-18201f51><button data-v-18201f51>Return to top</button><!----></div></div><aside class="VPSidebar" data-v-255ec12d data-v-845b8fc6><div class="curtain" data-v-845b8fc6></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-845b8fc6><span class="visually-hidden" id="sidebar-aria-label" data-v-845b8fc6> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Java</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E6%96%B0%E7%89%B9%E6%80%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java新特性</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E9%9B%86%E5%90%88.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java集合</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E9%AB%98%E7%BA%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java高级</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Linux</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/Linux%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Linux基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/Linux%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Linux新特性</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/Shell.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Shell脚本</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/%E5%AE%9E%E7%94%A8%E8%84%9A%E6%9C%AC.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>实用脚本</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/%E8%BD%AF%E4%BB%B6%E9%83%A8%E7%BD%B2.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>软件部署</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Nginx</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Nginx/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Nginx/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Nginx/%E5%AE%9E%E6%88%98%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>实战篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Nginx/%E9%9D%A2%E8%AF%95%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>面试篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible has-active" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>SSM</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/SSM/Maven.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Maven</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/SSM/Spring.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Spring</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/SSM/SpringMVC.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringMVC</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/SSM/SpringBatch.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringBatch</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>SpringBoot</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/3%E3%80%81SpringBoot/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/3%E3%80%81SpringBoot/%E5%BA%94%E7%94%A8%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>应用篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/3%E3%80%81SpringBoot/%E6%96%B0%E7%89%B9%E6%80%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>新特性</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/3%E3%80%81SpringBoot/%E8%BF%90%E7%BB%B4&amp;%E5%8E%9F%E7%90%86.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>运维&原理</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>SpringCloud</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringCloud</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E5%BF%85%E5%A4%87/Sentinel.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Sentinel</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>SpringSecurity</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/SpringSecurity/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringSecurity基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/SpringSecurity/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringSecurity进阶篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/SpringSecurity/%E9%AB%98%E7%BA%A7%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringSecurity高级篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Mybatis & MybatisPlus</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Mybatis&amp;MybatisPlus/Mybatis.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Mybatis</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Mybatis&amp;MybatisPlus/MybatisPlus.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MybatisPlus</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Mybatis&amp;MybatisPlus/JPA.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>JPA</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Git & ChatGPT</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/Git.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Git</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/Github.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Github</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/ChatGPT.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ChatGPT</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/Jenkins.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Jenkins</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/Netty.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Netty</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>数据库</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>MySQL</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E4%BC%98%E5%8C%96.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL优化</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E8%AE%BE%E8%AE%A1.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL设计</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E8%BF%90%E7%BB%B4.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL运维</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>分库分表</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>Redis</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E4%BC%98%E5%8C%96.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis优化</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E5%8E%9F%E7%90%86.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis原理</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E9%AB%98%E7%BA%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis高级</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E5%AE%9E%E6%88%98.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis实战</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/%E6%9C%AC%E5%9C%B0%E7%BC%93%E5%AD%98.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>本地缓存</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>MongoDB</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MongoDB/%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MongoDB基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MongoDB/%E6%95%B4%E5%90%88.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MongoDB进阶</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>ElasticSearch</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/ElasticSearch/1%E3%80%81ES%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ES基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/ElasticSearch/3%E3%80%81ES%E9%AB%98%E7%BA%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ES高级</p><!--]--></a><!----></div><!----></div><!--]--></div></section><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/influxdb.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>InfluxDB</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Neo4j.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Neo4j</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>高并发 & 秒杀 & 分布式</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%B8%89%E9%AB%98/%E5%88%86%E5%B8%83%E5%BC%8F.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>分布式理论</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E5%BF%85%E5%A4%87/%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>分布式锁</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%B8%89%E9%AB%98/%E7%A7%92%E6%9D%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>秒杀</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%B8%89%E9%AB%98/%E9%AB%98%E5%8F%AF%E7%94%A8.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>高可用</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%B8%89%E9%AB%98/%E9%AB%98%E5%B9%B6%E5%8F%91.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>高并发</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>云原生</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%BA%91%E5%8E%9F%E7%94%9F/Docker.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Docker</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%BA%91%E5%8E%9F%E7%94%9F/K8S.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>K8S</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>可视化 & 监控</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E5%8F%AF%E8%A7%86%E5%8C%96%20&amp;%20%E7%9B%91%E6%8E%A7/%E7%9B%91%E6%8E%A7%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>监控基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E5%8F%AF%E8%A7%86%E5%8C%96%20&amp;%20%E7%9B%91%E6%8E%A7/%E7%9B%91%E6%8E%A7%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>监控进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E5%8F%AF%E8%A7%86%E5%8C%96%20&amp;%20%E7%9B%91%E6%8E%A7/%E5%8F%AF%E8%A7%86%E5%8C%96%E5%A4%A7%E5%B1%8F.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>可视化大屏</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E5%8F%AF%E8%A7%86%E5%8C%96%20&amp;%20%E7%9B%91%E6%8E%A7/Zabbix.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Zabbix</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>学前端</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>HTML+CSS</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/1%E3%80%81HTML+CSS/HTML%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>HTML基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/1%E3%80%81HTML+CSS/CSS%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>CSS基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/1%E3%80%81HTML+CSS/%E7%BD%91%E9%A1%B5%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>网页进阶</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>JS+TS</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/JS%20%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>JS基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/JS%20%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>JS进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/ES6%20%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ES6基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/ES6%20%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ES6进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/TypeScript.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>TS基础</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>NodeJS</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/4%E3%80%81Node/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Node基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/4%E3%80%81Node/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Node进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/4%E3%80%81Node/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>项目实战</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>Vue</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/3%E3%80%81Vue/Vue3/Vue3%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Vue3进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/3%E3%80%81Vue/Vue3/Vue3%E9%AB%98%E7%BA%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Vue3高级</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/3%E3%80%81Vue/Vue3/Vue3%E6%96%B0%E8%AF%AD%E6%B3%95.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Vue3新语法</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/3%E3%80%81Vue/Vue2/Vue2%E9%A1%B9%E7%9B%AE.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>项目实战</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>小程序</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/5%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>小程序基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/5%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E4%BC%98%E5%8C%96.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>小程序优化</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/5%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F/uniapp.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>uniapp</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/5%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E9%A1%B9%E7%9B%AE.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>项目实战</p><!--]--></a><!----></div><!----></div><!--]--></div></section><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>计算机基础</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>数据结构</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>操作系统</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>设计模式</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E7%BD%91%E7%BB%9C%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>计算机网络</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/UML.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>UML</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E7%AE%97%E6%B3%95/LeetCode.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>LeetCode</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>项目实战</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>云尚办公</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E4%BA%91%E5%B0%9A%E5%8A%9E%E5%85%AC/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>小兔鲜</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E5%B0%8F%E5%85%94%E9%B2%9C/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E5%B0%8F%E5%85%94%E9%B2%9C/%E8%BF%9B%E9%98%B6%E7%AF%871.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇1</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E5%B0%8F%E5%85%94%E9%B2%9C/%E8%BF%9B%E9%98%B6%E7%AF%872.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇2</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>地图</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E7%99%BE%E5%BA%A6%E5%9C%B0%E5%9B%BE/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E7%99%BE%E5%BA%A6%E5%9C%B0%E5%9B%BE/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>苍穹外卖</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E8%8B%8D%E7%A9%B9%E5%A4%96%E5%8D%96/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>黑马头条</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%BB%91%E9%A9%AC%E5%A4%B4%E6%9D%A1/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%BB%91%E9%A9%AC%E5%A4%B4%E6%9D%A1/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%BB%91%E9%A9%AC%E5%A4%B4%E6%9D%A1/%E8%BF%9B%E9%98%B6%E7%AF%872.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇2</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%BB%91%E9%A9%AC%E5%A4%B4%E6%9D%A1/%E9%AB%98%E7%BA%A7%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>高级篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E6%94%AF%E4%BB%98.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>支付</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%A1%B9%E7%9B%AE%E6%8E%A8%E8%8D%90.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>项目推荐</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0" data-v-845b8fc6 data-v-9b797284><!----><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/team.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>团队成员</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-255ec12d data-v-669faec9><div class="VPDoc has-sidebar has-aside" data-v-669faec9 data-v-6b87e69f><!--[--><!--]--><div class="container" data-v-6b87e69f><div class="aside" data-v-6b87e69f><div class="aside-curtain" data-v-6b87e69f></div><div class="aside-container" data-v-6b87e69f><div class="aside-content" data-v-6b87e69f><div class="VPDocAside" data-v-6b87e69f data-v-3f215769><!--[--><!--]--><!--[--><!--]--><div class="VPDocAsideOutline" data-v-3f215769 data-v-ff0f39c8><div class="content" data-v-ff0f39c8><div class="outline-marker" data-v-ff0f39c8></div><div class="outline-title" data-v-ff0f39c8>On this page</div><nav aria-labelledby="doc-outline-aria-label" data-v-ff0f39c8><span class="visually-hidden" id="doc-outline-aria-label" data-v-ff0f39c8> Table of Contents for current page </span><ul class="root" data-v-ff0f39c8 data-v-d0ee3533><!--[--><!--]--></ul></nav></div></div><!--[--><!--]--><div class="spacer" data-v-3f215769></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-6b87e69f><div class="content-container" data-v-6b87e69f><!--[--><!--]--><!----><main class="main" data-v-6b87e69f><div style="position:relative;" class="vp-doc _notebook_SSM_Spring" data-v-6b87e69f><div><p>其实spring支持9种表达式，<code>execution</code>只是其中一种。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207221551516.png" alt="image-20220722155108406" style="zoom:50%;"><h2 id="aop-开发明确的事项" tabindex="-1">AOP 开发明确的事项 <a class="header-anchor" href="#aop-开发明确的事项" aria-label="Permalink to &quot;AOP 开发明确的事项&quot;">​</a></h2><ul><li><p>aop：面向切面编程</p></li><li><p>aop底层实现：基于JDK的动态代理 和 基于Cglib的动态代理</p></li><li><p>aop的重点概念：</p><div class="language-c"><button title="Copy Code" class="copy"></button><span class="lang">c</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">Pointcut（切入点）：被增强的方法</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">Advice（通知</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;"> 增强）：封装增强业务逻辑的方法</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">Aspect（切面）：切点</span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;">通知</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">Weaving（织入）：将切点与通知结合的过程</span></span></code></pre></div><p>开发明确事项：</p><div class="language-c"><button title="Copy Code" class="copy"></button><span class="lang">c</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">谁是切点（切点表达式配置）</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">谁是通知（切面类中的增强方法）</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">将切点和通知进行织入配置</span></span></code></pre></div></li></ul><h2 id="注解通知的类型" tabindex="-1">注解通知的类型 <a class="header-anchor" href="#注解通知的类型" aria-label="Permalink to &quot;注解通知的类型&quot;">​</a></h2><h3 id="详解" tabindex="-1">详解 <a class="header-anchor" href="#详解" aria-label="Permalink to &quot;详解&quot;">​</a></h3><ul><li>JoinPoint：适用于前置、后置、返回后、抛出异常后通知</li><li>ProceedingJoinPoint：适用于环绕通知</li></ul><p>通知的配置语法：@通知注解(“切点表达式&quot;)</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img/image-20211002184540992.png" alt="image-20211002184540992" style="zoom:80%;"><p>为了更好的理解这几种通知类型，我们来看一张图</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.11/202206031300916.png" alt="1630166147697" style="zoom:80%;"><p>(1)前置通知，<code>追加功能到方法执行前</code>,类似于在代码1或者代码2添加内容</p><p>(2)后置通知,<code>追加功能到方法执行后</code>,<code>不管方法执行的过程中有没有抛出异常都会执行</code>，类似于在代码5添加内容</p><p>(3)返回后通知,<code>追加功能到方法执行后，只有方法正常执行结束后才进行</code>,类似于在代码3添加内容，如果方法执行抛出异常，返回后通知将不会被添加</p><p>(4)抛出异常后通知,<code>追加功能到方法抛出异常后，只有方法执行出异常才进行</code>,类似于在代码4添加内容，只有方法抛出异常后才会被添加</p><p>(5)环绕通知,<code>环绕通知功能比较强大，它可以追加功能到方法执行的前后，这也是比较常用的方式，它可以实现其他四种通知类型的功能</code>。</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">ElementType</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Retention</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">RetentionPolicy</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Target</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Target</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">ElementType</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">METHOD</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Retention</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">RetentionPolicy</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">RUNTIME</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">interface</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Time</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h3 id="环绕通知-重点" tabindex="-1">环绕通知(重点) <a class="header-anchor" href="#环绕通知-重点" aria-label="Permalink to &quot;环绕通知(重点)&quot;">​</a></h3><p>环绕通知注意事项</p><ol><li>环绕通知必须依赖形参<code>ProceedingJoinPoint</code>才能实现对原始方法的调用，进而实现原始方法调用前后同时添加通知</li><li>通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行</li><li><code>对原始方法的调用可以不接收返回值，通知方法设置成void即可，如果接收返回值，最好设定为Object类型</code></li><li>原始方法的返回值如果是void类型，<code>通知方法的返回值类型可以设置成void,也可以设置成Object</code></li><li>由于无法预知原始方法运行后是否会抛出异常，<code>因此环绕通知方法必须要处理Throwable异常</code></li></ol><p>注意：<code>环绕通知返回值会取代最后方法返回值，因此可以对最后方法进行增强</code></p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Around</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">@annotation(com.it.aop.Time)</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">methodExporter</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">ProceedingJoinPoint</span><span style="color:#A6ACCD;"> joinPoint</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> throws Throwable </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">前置增强</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//调用目标方法</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> proceed </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> joinPoint</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">proceed</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">后置增强</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//返回结果,这个结果会传递给被代理对象的方法，会取代原来结果，如果不改就是原样</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//如果修改返回结果，要和原来返回结果相同</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> proceed</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.11/202206031541104.png" alt="image-20220603154146051" style="zoom:80%;"><h3 id="前置通知" tabindex="-1">前置通知 <a class="header-anchor" href="#前置通知" aria-label="Permalink to &quot;前置通知&quot;">​</a></h3><p>该通知在方法执行之前执行，只需在公共方法上加@Before注解，就能定义前置通知：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Before</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">pointcut()</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">beforeLog</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">JoinPoint</span><span style="color:#A6ACCD;"> joinPoint</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">打印请求日志</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h3 id="后置通知" tabindex="-1">后置通知 <a class="header-anchor" href="#后置通知" aria-label="Permalink to &quot;后置通知&quot;">​</a></h3><p>该通知在方法执行之后执行，只需在公共方法上加@After注解，就能定义后置通知：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">After</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">pointcut()</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">afterLog</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">JoinPoint</span><span style="color:#A6ACCD;"> joinPoint</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">打印响应日志</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h4 id="结果通知-了解" tabindex="-1">结果通知(了解) <a class="header-anchor" href="#结果通知-了解" aria-label="Permalink to &quot;结果通知(了解)&quot;">​</a></h4><p>该通知在方法结束后执行，能够获取方法返回结果，只需在公共方法上加@AfterReturning注解，就能定义结果通知：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">AfterReturning</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">pointcut</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">@annotation(com.it.aop.Time)</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">returning</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">result</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">afterReturning</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> result</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> throws Exception </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//获取@Around返回参数</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">ObjectMapper</span><span style="color:#A6ACCD;"> mapper </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">ObjectMapper</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">业务层接口返回结果: </span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> mapper</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">writeValueAsString</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">result</span><span style="color:#89DDFF;">));</span></span>
<span class="line"><span style="color:#A6ACCD;">    log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">业务层接口返回结果: {}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> result</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h3 id="异常通知" tabindex="-1">异常通知 <a class="header-anchor" href="#异常通知" aria-label="Permalink to &quot;异常通知&quot;">​</a></h3><p>该通知在方法抛出异常之后执行，只需在公共方法上加@AfterThrowing注解，就能定义异常通知：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">AfterThrowing</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">pointcut</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">pointcut()</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">throwing</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">e</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">afterThrowing</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">JoinPoint</span><span style="color:#A6ACCD;"> joinPoint</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Throwable</span><span style="color:#A6ACCD;"> e</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">异常：</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;">e</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h2 id="joinpoint-获取参数" tabindex="-1">JoinPoint(获取参数) <a class="header-anchor" href="#joinpoint-获取参数" aria-label="Permalink to &quot;JoinPoint(获取参数)&quot;">​</a></h2><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207221555445.png" alt="image-20220722155505362" style="zoom:50%;"><h3 id="获取正常参数" tabindex="-1">获取正常参数 <a class="header-anchor" href="#获取正常参数" aria-label="Permalink to &quot;获取正常参数&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">*</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Target</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">ElementType</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">METHOD</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Retention</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">RetentionPolicy</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">RUNTIME</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">interface</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Time</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Around</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">@annotation(com.it.aop.Time)</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">methodExporter</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">ProceedingJoinPoint</span><span style="color:#A6ACCD;"> joinPoint</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> throws Throwable </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 获取返回结果</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> proceed </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> joinPoint</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">proceed</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 获取传入参数</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">Object</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> args </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> joinPoint</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getArgs</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 获取类名</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> name </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> joinPoint</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getSignature</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">getDeclaringTypeName</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 获取方法名</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> methodName </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> joinPoint</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getSignature</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">getName</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 获取方法参数类型和方法名(详细版本)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">Signature</span><span style="color:#A6ACCD;"> signature </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> joinPoint</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getSignature</span><span style="color:#89DDFF;">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">传入参数：{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> Arrays</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">toString</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">args</span><span style="color:#89DDFF;">));</span></span>
<span class="line"><span style="color:#A6ACCD;">    log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">返回结果：{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> proceed</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">方法名：{}.{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> name</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> methodName</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">详细版方法名：{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> signature</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> proceed</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>测试</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">//在这里直接把注解写在方法上即可</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Time</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">GetMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">list1</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Map</span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">String</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> String</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">list</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> name</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> age</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">Map</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> result </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">LinkedHashMap</span><span style="color:#89DDFF;">&lt;&gt;();</span></span>
<span class="line"><span style="color:#A6ACCD;">    result</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">put</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">name</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    result</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">put</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">age</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">age</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> result</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.11/202206031703217.png" alt="image-20220603170348168" style="zoom:80%;"><h3 id="获取注解内部参数" tabindex="-1">获取注解内部参数 <a class="header-anchor" href="#获取注解内部参数" aria-label="Permalink to &quot;获取注解内部参数&quot;">​</a></h3><p>如果要获取注解内部的值，主要修改</p><p>新增aop注解</p><div class="language-xml"><button title="Copy Code" class="copy"></button><span class="lang">xml</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">org.springframework.boot</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">spring-boot-starter-aop</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span></code></pre></div><p>加上参数</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Target</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">ElementType</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">METHOD</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Retention</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">RetentionPolicy</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">RUNTIME</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">interface</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Time</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 描述内容</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">description</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">default</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>切面中获取</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">注解内部值     : {}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">((</span><span style="color:#A6ACCD;">MethodSignature</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> joinPoint</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getSignature</span><span style="color:#89DDFF;">())</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getMethod</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">getAnnotation</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">Time</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">description</span><span style="color:#89DDFF;">());</span></span></code></pre></div><p>传入参数</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">//在这里直接把注解写在方法上即可</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Time</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">description</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">这是list方法</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.11/202206031706366.png" alt="image-20220603170611323" style="zoom:80%;"><h2 id="基于注解的-aop-开发" tabindex="-1">基于注解的 AOP 开发 <a class="header-anchor" href="#基于注解的-aop-开发" aria-label="Permalink to &quot;基于注解的 AOP 开发&quot;">​</a></h2><h3 id="依赖" tabindex="-1">依赖 <a class="header-anchor" href="#依赖" aria-label="Permalink to &quot;依赖&quot;">​</a></h3><div class="language-xml"><button title="Copy Code" class="copy"></button><span class="lang">xml</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">org.aspectj</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">aspectjweaver</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span></code></pre></div><h3 id="aop通知获取参数和返回值" tabindex="-1">AOP通知获取参数和返回值 <a class="header-anchor" href="#aop通知获取参数和返回值" aria-label="Permalink to &quot;AOP通知获取参数和返回值&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">ElementType</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Retention</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">RetentionPolicy</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Target</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//作用在方法上</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Target</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">ElementType</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">METHOD</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//@Retention作用是被它定义的注解要保留多久，RunTime运行时</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Retention</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">RetentionPolicy</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">RUNTIME</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//接口+@，表示说明注解</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">interface</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Time</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">com</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">fasterxml</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">jackson</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">databind</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">ObjectMapper</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">lombok</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">extern</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">slf4j</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Slf4j</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">aspectj</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">ProceedingJoinPoint</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">aspectj</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Around</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">aspectj</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Aspect</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">stereotype</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Component</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">util</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Date</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//关键代码1：说明当前对象是一个切面</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Aspect</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//关键代码2：允许Spring IOC对当前对象实例化并管理</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Component</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Slf4j</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">TimeAspect</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//@Around环绕通知，最强大的通知类型，可以控制方法的入参、执行、返回结果等各方面细节</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//这里写的是接口路径</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Around</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">@annotation(com.it.aop.Time)</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">methodExporter</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">ProceedingJoinPoint</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">joinPoint</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Throwable</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">ObjectMapper</span><span style="color:#A6ACCD;"> mapper </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">ObjectMapper</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 获取参数</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> jsonParam </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> mapper</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">writeValueAsString</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">joinPoint</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getArgs</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//计算方法执行时间</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">long</span><span style="color:#A6ACCD;"> st </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Date</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">getTime</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//获取方法返回值</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> proceed </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> joinPoint</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">proceed</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">long</span><span style="color:#A6ACCD;"> et </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Date</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">getTime</span><span style="color:#89DDFF;">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//将返回结果json序列化</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> jsonResult </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">null;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">proceed </span><span style="color:#89DDFF;">!=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">null)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            jsonResult </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> mapper</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">writeValueAsString</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">proceed</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#89DDFF;font-style:italic;">else</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 异常处理</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">throw</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">RuntimeException</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">返回值为空</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//模拟上报服务器过程</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">正在上报服务器：</span><span style="color:#A6ACCD;">\n</span><span style="color:#C3E88D;">target:{}.{}</span><span style="color:#A6ACCD;">\n</span><span style="color:#C3E88D;">execution:{}ms,</span><span style="color:#A6ACCD;">\n</span><span style="color:#C3E88D;">parameter:{}</span><span style="color:#A6ACCD;">\n</span><span style="color:#C3E88D;">result:{}</span><span style="color:#89DDFF;">&quot;</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">joinPoint</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getTarget</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">getClass</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">getSimpleName</span><span style="color:#89DDFF;">()</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">joinPoint</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getSignature</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">getName</span><span style="color:#89DDFF;">()</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">,(</span><span style="color:#A6ACCD;">et</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">st</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">jsonParam</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">jsonResult</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> proceed</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">//在这里直接把注解写在方法上即可</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Time</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">GetMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">list</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Map</span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">String</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> String</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">list</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> page</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> rows</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">Map</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> result </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">LinkedHashMap</span><span style="color:#89DDFF;">&lt;&gt;();</span></span>
<span class="line"><span style="color:#A6ACCD;">    result</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">put</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">code</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">0</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    result</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">put</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">message</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">success</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//休眠测试用</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;font-style:italic;">try</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        Thread</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">sleep</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Random</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">nextInt</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">1000</span><span style="color:#89DDFF;">));</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">catch</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">InterruptedException</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">e</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        e</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">printStackTrace</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> result</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.11/202206031249891.png" alt="image-20220603124944832" style="zoom:80%;"><h2 id="获取方法入参和时间示例" tabindex="-1">获取方法入参和时间示例 <a class="header-anchor" href="#获取方法入参和时间示例" aria-label="Permalink to &quot;获取方法入参和时间示例&quot;">​</a></h2><div class="language-xml"><button title="Copy Code" class="copy"></button><span class="lang">xml</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">org.aspectj</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">aspectjweaver</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span></code></pre></div><h3 id="设置注解" tabindex="-1">设置注解 <a class="header-anchor" href="#设置注解" aria-label="Permalink to &quot;设置注解&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">//作用在方法上</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Target</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">ElementType</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">METHOD</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//@Retention作用是被它定义的注解要保留多久，RunTime运行时</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Retention</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">RetentionPolicy</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">RUNTIME</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//接口+@，表示说明注解</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">interface</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Time</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h3 id="设置切面" tabindex="-1">设置切面 <a class="header-anchor" href="#设置切面" aria-label="Permalink to &quot;设置切面&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">com</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">fasterxml</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">jackson</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">databind</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">ObjectMapper</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">lombok</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">extern</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">slf4j</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Slf4j</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">aspectj</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">ProceedingJoinPoint</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">aspectj</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Around</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">aspectj</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Aspect</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">stereotype</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Component</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">util</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Date</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//关键代码1：说明当前对象是一个切面</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Aspect</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//关键代码2：允许Spring IOC对当前对象实例化并管理</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Component</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Slf4j</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">TimeAspect</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//关键代码3：说明切面的作用范围，任何增加@MethodExporter的目标方法都将在执行方法前执行该切面方法</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//@Around环绕通知，最强大的通知类型，可以控制方法的入参、执行、返回结果等各方面细节</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//这里写的是接口路径</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Around</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">@annotation(com.it.aop.Time)</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">methodExporter</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">ProceedingJoinPoint</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">joinPoint</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Throwable</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//计算方法执行时间</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">long</span><span style="color:#A6ACCD;"> st </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Date</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">getTime</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//执行目标方法，获取方法返回值</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> proceed </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> joinPoint</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">proceed</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">long</span><span style="color:#A6ACCD;"> et </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Date</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">getTime</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//这里用于封装结果json</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">ObjectMapper</span><span style="color:#A6ACCD;"> mapper </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">ObjectMapper</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//获取执行方法的参数getArgs</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> jsonParam </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> mapper</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">writeValueAsString</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">joinPoint</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getArgs</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//将返回结果json序列化</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> jsonResult </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">null;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">proceed </span><span style="color:#89DDFF;">!=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">null)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            jsonResult </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> mapper</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">writeValueAsString</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">proceed</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#89DDFF;font-style:italic;">else</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            jsonResult </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">null</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//模拟上报服务器过程</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">正在上报服务器：</span><span style="color:#A6ACCD;">\n</span><span style="color:#C3E88D;">target:{}.{}</span><span style="color:#A6ACCD;">\n</span><span style="color:#C3E88D;">execution:{}ms,</span><span style="color:#A6ACCD;">\n</span><span style="color:#C3E88D;">parameter:{}</span><span style="color:#A6ACCD;">\n</span><span style="color:#C3E88D;">result:{}</span><span style="color:#89DDFF;">&quot;</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">joinPoint</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getTarget</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">getClass</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">getSimpleName</span><span style="color:#89DDFF;">()</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">joinPoint</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getSignature</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">getName</span><span style="color:#89DDFF;">()</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">,(</span><span style="color:#A6ACCD;">et</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">st</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">jsonParam</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">jsonResult</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> proceed</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h3 id="测试使用" tabindex="-1">测试使用 <a class="header-anchor" href="#测试使用" aria-label="Permalink to &quot;测试使用&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">//在这里直接把注解写在方法上即可</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Time</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">GetMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">list</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Map</span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">String</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> String</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">list</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> page</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> rows</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">Map</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> result </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">LinkedHashMap</span><span style="color:#89DDFF;">&lt;&gt;();</span></span>
<span class="line"><span style="color:#A6ACCD;">    result</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">put</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">code</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">0</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    result</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">put</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">message</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">success</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//休眠测试用</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;font-style:italic;">try</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        Thread</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">sleep</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Random</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">nextInt</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">1000</span><span style="color:#89DDFF;">));</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">catch</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">InterruptedException</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">e</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        e</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">printStackTrace</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> result</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.11/202206031200567.png" alt="image-20220603120036507" style="zoom:80%;"><h2 id="修改传入参数示例" tabindex="-1">修改传入参数示例 <a class="header-anchor" href="#修改传入参数示例" aria-label="Permalink to &quot;修改传入参数示例&quot;">​</a></h2><p>将传入的参数去掉前后空格</p><h3 id="设置注解-1" tabindex="-1">设置注解 <a class="header-anchor" href="#设置注解-1" aria-label="Permalink to &quot;设置注解&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">ElementType</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Retention</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">RetentionPolicy</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Target</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//作用在方法上</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Target</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">ElementType</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">METHOD</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//@Retention作用是被它定义的注解要保留多久，RunTime运行时</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Retention</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">RetentionPolicy</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">RUNTIME</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//接口+@，表示说明注解</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">interface</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">TrimStr</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h3 id="设置切面-1" tabindex="-1">设置切面 <a class="header-anchor" href="#设置切面-1" aria-label="Permalink to &quot;设置切面&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">lombok</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">extern</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">slf4j</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Slf4j</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">aspectj</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">ProceedingJoinPoint</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">aspectj</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Around</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">aspectj</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Aspect</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">stereotype</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Component</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//关键代码1：说明当前对象是一个切面</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Aspect</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//关键代码2：允许Spring IOC对当前对象实例化并管理</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Component</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Slf4j</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">TrimStrAspect</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//关键代码3：说明切面的作用范围，任何增加@MethodExporter的目标方法都将在执行方法前执行该切面方法</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//@Around环绕通知，最强大的通知类型，可以控制方法的入参、执行、返回结果等各方面细节</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//这里写的是接口路径</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Around</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">@annotation(com.it.aop.TrimStr)</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">methodExporter</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">ProceedingJoinPoint</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">pjp</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Throwable</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//获取原始方法的参数</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">Object</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> args </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> pjp</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getArgs</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;"> args</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">length</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">++)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">//判断参数是不是字符串</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">args</span><span style="color:#89DDFF;">[</span><span style="color:#A6ACCD;">i</span><span style="color:#89DDFF;">].</span><span style="color:#82AAFF;">getClass</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">equals</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">String</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">)){</span></span>
<span class="line"><span style="color:#A6ACCD;">                args</span><span style="color:#89DDFF;">[</span><span style="color:#A6ACCD;">i</span><span style="color:#89DDFF;">]</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> args</span><span style="color:#89DDFF;">[</span><span style="color:#A6ACCD;">i</span><span style="color:#89DDFF;">].</span><span style="color:#82AAFF;">toString</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">trim</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//将修改后的参数传入到原始方法z的执行中，注意：一定要传入args，不然不生效</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//也就是执行时传入修改过的参数</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> pjp</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">proceed</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">args</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h3 id="测试使用-1" tabindex="-1">测试使用 <a class="header-anchor" href="#测试使用-1" aria-label="Permalink to &quot;测试使用&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">//在这里直接把注解写在方法上即可</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">TrimStr</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">GetMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">list1</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Map</span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">String</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> String</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">list</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> name</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> age</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">Map</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> result </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">LinkedHashMap</span><span style="color:#89DDFF;">&lt;&gt;();</span></span>
<span class="line"><span style="color:#A6ACCD;">    result</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">put</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">name</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    result</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">put</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">age</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">age</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> result</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h2 id="aop记录日志示例" tabindex="-1">AOP记录日志示例 <a class="header-anchor" href="#aop记录日志示例" aria-label="Permalink to &quot;AOP记录日志示例&quot;">​</a></h2><h3 id="注解示例" tabindex="-1">注解示例 <a class="header-anchor" href="#注解示例" aria-label="Permalink to &quot;注解示例&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">ElementType</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Retention</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">RetentionPolicy</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Target</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Retention</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">RetentionPolicy</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">RUNTIME</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Target</span><span style="color:#89DDFF;">({</span><span style="color:#A6ACCD;">ElementType</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">METHOD</span><span style="color:#89DDFF;">})</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">interface</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Log</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 加入方法描述</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">description</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">default</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h3 id="切面配置" tabindex="-1">切面配置 <a class="header-anchor" href="#切面配置" aria-label="Permalink to &quot;切面配置&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">com</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">fasterxml</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">jackson</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">databind</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">ObjectMapper</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">lombok</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">extern</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">slf4j</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Slf4j</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">aspectj</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">JoinPoint</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">aspectj</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">ProceedingJoinPoint</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">aspectj</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">*</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">aspectj</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">reflect</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">MethodSignature</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">stereotype</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Component</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">web</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">context</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">request</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">RequestContextHolder</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">web</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">context</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">request</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">ServletRequestAttributes</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">javax</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">servlet</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">http</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">HttpServletRequest</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Aspect</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Component</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Slf4j</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">LogAspect</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 以自定义 @WebLog 注解为切点</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Pointcut</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">@annotation(com.it.aop.Log)</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 注解的无参构造</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Log</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 在切点之前织入</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Before</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Log()</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">doBefore</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">JoinPoint</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">joinPoint</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Throwable</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 开始打印请求日志</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">ServletRequestAttributes</span><span style="color:#A6ACCD;"> attributes </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">ServletRequestAttributes</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">                RequestContextHolder</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getRequestAttributes</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">HttpServletRequest</span><span style="color:#A6ACCD;"> request </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> attributes</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getRequest</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 打印请求相关参数</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">=============== Start ============</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 打印请求 url</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">URL            : {}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> request</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getRequestURL</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">toString</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 打印 Http method</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">HTTP Method    : {}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> request</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getMethod</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 打印调用 controller 的全路径以及执行方法</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Class Method   : {}.{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">                joinPoint</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getSignature</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">getDeclaringTypeName</span><span style="color:#89DDFF;">(),</span></span>
<span class="line"><span style="color:#A6ACCD;">                joinPoint</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getSignature</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">getName</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 打印请求的 IP</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">IP             : {}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> request</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getRemoteAddr</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 打印请求入参</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Request Args   : {}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">ObjectMapper</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">writeValueAsString</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">joinPoint</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getArgs</span><span style="color:#89DDFF;">()));</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 打印注解内部的值</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Annotation     : {}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">((</span><span style="color:#A6ACCD;">MethodSignature</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> joinPoint</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getSignature</span><span style="color:#89DDFF;">())</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getMethod</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">getAnnotation</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">Log</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">description</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 在切点之后织入</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">After</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Log()</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">doAfter</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Throwable</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 结束后打个分隔线，方便查看</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">=========== End ================</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 环绕</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Around</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Log()</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">doAround</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">ProceedingJoinPoint</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">proceedingJoinPoint</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Throwable</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//开始时间</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">long</span><span style="color:#A6ACCD;"> startTime </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> System</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">currentTimeMillis</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> result </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> proceedingJoinPoint</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">proceed</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 打印出参</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Response Args  : {}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">ObjectMapper</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">writeValueAsString</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">result</span><span style="color:#89DDFF;">));</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 执行耗时</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Time-Consuming : {} ms</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> System</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">currentTimeMillis</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;"> startTime</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> result</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h3 id="测试示例" tabindex="-1">测试示例 <a class="header-anchor" href="#测试示例" aria-label="Permalink to &quot;测试示例&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">GetMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">hello</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Log</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">description</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">这是描述方法</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">hello</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> name</span><span style="color:#89DDFF;">){</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Hello </span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;">name</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.11/202206031406949.png" alt="image-20220603140656895" style="zoom:80%;"><h1 id="spring事务" tabindex="-1">Spring事务 <a class="header-anchor" href="#spring事务" aria-label="Permalink to &quot;Spring事务&quot;">​</a></h1><h2 id="spring事务简介" tabindex="-1">Spring事务简介 <a class="header-anchor" href="#spring事务简介" aria-label="Permalink to &quot;Spring事务简介&quot;">​</a></h2><h3 id="事务的特性" tabindex="-1">事务的特性 <a class="header-anchor" href="#事务的特性" aria-label="Permalink to &quot;事务的特性&quot;">​</a></h3><p>事务（Transaction)是由一系列对系统中数据进行访问与更新的操作所组成的一个程序 执行逻辑单元（Unit)。事务具有四个特征，分别是原子性（Atomicity )、一致性（Consistency )、隔离性（Isolation) 和持久性（Durability),简称为事务的ACID特性。</p><ul><li>原子性（Atomicity）：原子性是指事务是一个不可分割的工作单位，事务中的操作要么都发生，要么都不发生。</li><li>一致性（Consistency）：事务执行前后数据的完整性必须保持一致。比如在转账事务操作中，事务执行前后金额的总数应保持不变。</li><li>隔离性（Isolation）：事务的隔离性是多个用户并发访问数据库时，数据库为每一个用户开启的事务，不能被其他事务的操作数据所干扰，多个并发事务之间要相互隔离。</li><li>持久性（Durability）：持久性是指一个事务一旦被提交，它对数据库中数据的改变就是永久性的，接下来即使数据库发生故障也不应该对其有任何影响。</li></ul><h3 id="事务并发的问题" tabindex="-1">事务并发的问题 <a class="header-anchor" href="#事务并发的问题" aria-label="Permalink to &quot;事务并发的问题&quot;">​</a></h3><p>1.丢失修改（丢失更新）：两个事务T1和T2读入同一数据并修改，T2的提交结果破坏了T1提交的结果，导致T1的修改被丢失。</p><p>2.不可重复读：事务T1读取某一数据后，事务T2对其做了修改，当事务T1再次读该数据时，得到与前一次不同的值。</p><p>3.幻读：事务T1按一定条件从数据库中读取了某些数据记录后，事务T2删除了其中部分记录，当T1再次按相同条件读取数据时，发现某些记录消失了；或者说事务T2插入了部分新记录，当T1再次按相同条件读取数据时，发现多出来了部分数据。</p><p>注意：幻读和不可重复读的主要区别在于：</p><ul><li><p>幻读针对的是查询结果为多个的场景，出现了数据的增加 or 减少</p></li><li><p>不可重复度读对的是某些特定的记录，这些记录的数据与之前不一致</p></li></ul><p>4.读“脏”数据：事务T1修改某一数据后，事务T2读取同一数据，然后T1由于某种原因操作被撤销（回滚），这时T1已修改过的数据恢复原值，T2读到的数据就与数据库中的真实数据不一致，这时T2读到的数据就为“脏”数据，即不正确的数据。</p><h3 id="相关概念介绍" tabindex="-1">相关概念介绍 <a class="header-anchor" href="#相关概念介绍" aria-label="Permalink to &quot;相关概念介绍&quot;">​</a></h3><ul><li>事务作用：<code>在数据层保障一系列的数据库操作同成功同失败</code></li><li>Spring事务作用：在数据层或**==业务层==**保障一系列的数据库操作同成功同失败</li></ul><p>数据层有事务我们可以理解，为什么业务层也需要处理事务呢?</p><p>注意：</p><blockquote><p>mysql默认隔离级别：可重复读 。在这种隔离级别下，所有事务前后多次的读取到的数据内容是不变的。也就是某个事务（在SpringBoot中表现为加了@Transactional注解的方法为一个事务整体）在执行的过程中，不允许其他事务进行相关字段的update操作（加行锁），但允许其他事务进行add操作，造成某个事务前后多次读取到的数据总量不一致的现象，从而产生幻读。这是mysql的默认事务隔离级别。</p><p>异常回滚类型：注解 @Transactional只对unchecked异常进行事务回滚，可以通过添加@Transactional(rollbackFor=Exception.class) 来对所有异常进行回滚。</p></blockquote><p>举个简单的例子，</p><ul><li><code>转账业务会有两次数据层的调用，一次是加钱一次是减钱</code></li><li><code>把事务放在数据层，加钱和减钱就有两个事务</code></li><li><code>没办法保证加钱和减钱同时成功或者同时失败</code></li><li><code>这个时候就需要将事务放在业务层进行处理</code></li></ul><p>Spring为了管理事务，提供了一个平台事务管理器<code>PlatformTransactionManager</code></p><p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.11/202206101720595.png" alt="1630243651541"></p><p><code>commit是用来提交事务，rollback是用来回滚事务</code>。</p><p>PlatformTransactionManager只是一个接口，Spring还为其提供了一个具体的实现:</p><p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.11/202206101720607.png" alt="1630243993380"></p><p>从名称上可以看出，我们只需要给它一个DataSource对象，它就可以帮你去在业务层管理事务。其内部采用的是JDBC的事务。所以说如果你持久层采用的是JDBC相关的技术，就可以采用这个事务管理器来管理你的事务。而Mybatis内部采用的就是JDBC的事务，所以后期我们Spring整合Mybatis就采用的这个DataSourceTransactionManager事务管理器。</p><p>事务管理在系统开发中是不可缺少的一部分，<code>Spring</code>提供了很好事务管理机制，主要分为<code>编程式事务</code>和<code>声明式事务</code>两种。</p><h3 id="事务实现代码" tabindex="-1">事务实现代码 <a class="header-anchor" href="#事务实现代码" aria-label="Permalink to &quot;事务实现代码&quot;">​</a></h3><p><strong>编程式事务</strong>：是指在代码中手动的管理事务的提交、回滚等操作，代码侵入性比较强，如下示例：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;font-style:italic;">try</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//TODO something</span></span>
<span class="line"><span style="color:#A6ACCD;">     transactionManager</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">commit</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">status</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">catch</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">e</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    transactionManager</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">rollback</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">status</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;font-style:italic;">throw</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">InvoiceApplyException</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">异常失败</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p><strong>声明式事务</strong>：基于<code>AOP</code>面向切面的，它将具体业务与事务处理部分解耦，代码侵入性很低，所以在实际开发中声明式事务用的比较多。声明式事务也有两种实现方式，一是基于<code>TX</code>和<code>AOP</code>的xml配置文件方式，二种就是基于@Transactional注解了。</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">GetMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">/test</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">test</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> insert </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> cityInfoDictMapper</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">insert</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">cityInfoDict</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h3 id="转账案例-需求分析" tabindex="-1">转账案例-需求分析 <a class="header-anchor" href="#转账案例-需求分析" aria-label="Permalink to &quot;转账案例-需求分析&quot;">​</a></h3><p>接下来通过一个案例来学习下Spring是如何来管理事务的。</p><p>先来分析下需求:</p><p>需求: 实现任意两个账户间转账操作</p><p>需求微缩: A账户减钱，B账户加钱</p><p>为了实现上述的业务需求，我们可以按照下面步骤来实现下: ①：<code>数据层提供基础操作，指定账户减钱（outMoney），指定账户加钱（inMoney）</code></p><p>②：<code>业务层提供转账操作（transfer），调用减钱与加钱的操作</code></p><p>③：<code>提供2个账号和操作金额执行转账操作</code></p><p>④：<code>基于Spring整合MyBatis环境搭建上述操作</code></p><h3 id="转账案例-实现事务" tabindex="-1">转账案例(实现事务) <a class="header-anchor" href="#转账案例-实现事务" aria-label="Permalink to &quot;转账案例(实现事务)&quot;">​</a></h3><h5 id="步骤1-准备数据库表" tabindex="-1">步骤1:准备数据库表 <a class="header-anchor" href="#步骤1-准备数据库表" aria-label="Permalink to &quot;步骤1:准备数据库表&quot;">​</a></h5><p>之前我们在整合Mybatis的时候已经创建了这个表,可以直接使用</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">create</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">database</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">spring_db</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">character</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">set</span><span style="color:#A6ACCD;"> utf8;</span></span>
<span class="line"><span style="color:#F78C6C;">use</span><span style="color:#A6ACCD;"> spring_db;</span></span>
<span class="line"><span style="color:#F78C6C;">create</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">table</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">tbl_account</span><span style="color:#A6ACCD;">(</span></span>
<span class="line"><span style="color:#A6ACCD;">    id </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">primary key</span><span style="color:#A6ACCD;"> auto_increment,</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">35</span><span style="color:#A6ACCD;">),</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">money</span><span style="color:#A6ACCD;"> double</span></span>
<span class="line"><span style="color:#A6ACCD;">);</span></span>
<span class="line"><span style="color:#F78C6C;">insert into</span><span style="color:#A6ACCD;"> tbl_account </span><span style="color:#F78C6C;">values</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">Tom</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span><span style="color:#F78C6C;">1000</span><span style="color:#A6ACCD;">);</span></span>
<span class="line"><span style="color:#F78C6C;">insert into</span><span style="color:#A6ACCD;"> tbl_account </span><span style="color:#F78C6C;">values</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">Jerry</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span><span style="color:#F78C6C;">1000</span><span style="color:#A6ACCD;">);</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.11/202206101809940.png" alt="image-20220610180901865" style="zoom:80%;"><h5 id="步骤2-创建项目导入jar包" tabindex="-1">步骤2:创建项目导入jar包 <a class="header-anchor" href="#步骤2-创建项目导入jar包" aria-label="Permalink to &quot;步骤2:创建项目导入jar包&quot;">​</a></h5><p>项目的pom.xml添加相关依赖</p><div class="language-xml"><button title="Copy Code" class="copy"></button><span class="lang">xml</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">dependencies</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">org.springframework.boot</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">spring-boot-starter-web</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#676E95;font-style:italic;">&lt;!--mysql依赖--&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">mysql</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">mysql-connector-java</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#676E95;font-style:italic;">&lt;!--mybatis-plus依赖--&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">com.baomidou</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">mybatis-plus-boot-starter</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">version</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">3.5.1</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">version</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#676E95;font-style:italic;">&lt;!--lombok依赖--&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">org.projectlombok</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">lombok</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#676E95;font-style:italic;">&lt;!-- 数据连接池 druid--&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">com.alibaba</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">druid</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">version</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">1.2.8</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">version</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">dependencies</span><span style="color:#89DDFF;">&gt;</span></span></code></pre></div><h5 id="步骤3-根据表创建模型类" tabindex="-1">步骤3:根据表创建模型类 <a class="header-anchor" href="#步骤3-根据表创建模型类" aria-label="Permalink to &quot;步骤3:根据表创建模型类&quot;">​</a></h5><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Data</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">account</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Serializable</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Integer</span><span style="color:#A6ACCD;"> id</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> name</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Double</span><span style="color:#A6ACCD;"> money</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h5 id="步骤4-创建mapper接口" tabindex="-1">步骤4:创建Mapper接口 <a class="header-anchor" href="#步骤4-创建mapper接口" aria-label="Permalink to &quot;步骤4:创建Mapper接口&quot;">​</a></h5><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">com</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">baomidou</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">mybatisplus</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">core</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">mapper</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">BaseMapper</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">com</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">it</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">entity</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">account</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">ibatis</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotations</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Mapper</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">ibatis</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotations</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Param</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">ibatis</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotations</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Update</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Mapper</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">interface</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">accountMapper</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">extends</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">BaseMapper</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">account</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Update</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">update tbl_account set money = money + #{money} where name = #{name}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">inMoney</span><span style="color:#89DDFF;">(@</span><span style="color:#C792EA;">Param</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">name</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Param</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">money</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Double</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">money</span><span style="color:#89DDFF;">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Update</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">update tbl_account set money = money - #{money} where name = #{name}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">outMoney</span><span style="color:#89DDFF;">(@</span><span style="color:#C792EA;">Param</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">name</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Param</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">money</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Double</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">money</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h5 id="步骤5-创建service接口和实现类" tabindex="-1">步骤5:创建Service接口和实现类 <a class="header-anchor" href="#步骤5-创建service接口和实现类" aria-label="Permalink to &quot;步骤5:创建Service接口和实现类&quot;">​</a></h5><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">interface</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">AccountService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">    /**</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">     * 转账操作</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">     * </span><span style="color:#F78C6C;font-style:italic;">@param</span><span style="color:#676E95;font-style:italic;"> </span><span style="color:#A6ACCD;font-style:italic;">out</span><span style="color:#676E95;font-style:italic;"> 传出方</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">     * </span><span style="color:#F78C6C;font-style:italic;">@param</span><span style="color:#676E95;font-style:italic;"> </span><span style="color:#A6ACCD;font-style:italic;">in</span><span style="color:#676E95;font-style:italic;"> 转入方</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">     * </span><span style="color:#F78C6C;font-style:italic;">@param</span><span style="color:#676E95;font-style:italic;"> </span><span style="color:#A6ACCD;font-style:italic;">money</span><span style="color:#676E95;font-style:italic;"> 金额</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">transfer</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">out</span><span style="color:#89DDFF;">,</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">in</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">,</span><span style="color:#C792EA;">Double</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">money</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">com</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">it</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">mapper</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">accountMapper</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">stereotype</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Service</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">transaction</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">javax</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Resource</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">AccountServiceImpl</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">AccountService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Resource</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> accountMapper accountMapper</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//out，in是name</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//不加该注解，那么依旧是只有一个改变，否则就是两个都不变</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">transfer</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">out</span><span style="color:#89DDFF;">,</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">in</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">,</span><span style="color:#C792EA;">Double</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">money</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        accountMapper</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">outMoney</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">money</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//抛出异常，测试事务</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#89DDFF;">/</span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        accountMapper</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">inMoney</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">in</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">money</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h5 id="步骤6-编写测试" tabindex="-1">步骤6:编写测试 <a class="header-anchor" href="#步骤6-编写测试" aria-label="Permalink to &quot;步骤6:编写测试&quot;">​</a></h5><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Resource</span></span>
<span class="line"><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">AccountService</span><span style="color:#A6ACCD;"> accountService</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Test</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">testTransfer</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//Tom -&gt; Jack 转账100元</span></span>
<span class="line"><span style="color:#A6ACCD;">    accountService</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">transfer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Tom</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Jerry</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">100.0</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h2 id="事务知识" tabindex="-1">事务知识 <a class="header-anchor" href="#事务知识" aria-label="Permalink to &quot;事务知识&quot;">​</a></h2><p>上述环境，运行单元测试类，会执行转账操作，<code>Tom</code>的账户会减少100，<code>Jerry</code>的账户会加100。</p><p>这是正常情况下的运行结果，但是如果在转账的过程中出现了异常，如:</p><p>这个时候就模拟了转账过程中出现异常的情况，正确的操作应该是转账出问题了，<code>Tom</code>应该还是900，<code>Jerry</code>应该还是1100，但是真正运行后会发现，并没有像我们想象的那样，<code>Tom</code>账户为800而<code>Jerry</code>还是1100,100块钱凭空消息了，银行乐疯了。如果把转账换个顺序，银行就该哭了。</p><p>不管哪种情况，都是不允许出现的，对刚才的结果我们做一个分析:</p><p>①：<code>程序正常执行时，账户金额A减B加，没有问题</code></p><p>②：<code>程序出现异常后，转账失败，但是异常之前操作成功，异常之后操作失败，整体业务失败</code></p><p>当程序出问题后，我们需要让事务进行回滚，而且这个事务应该是加在业务层上，而Spring的事务管理就是用来解决这类问题的。</p><p>Spring提供了一个@EnableTransactionManagement注解在配置类上来开启声明式事务的支持（开启注解支持）。使用了@EnableTransactionManagement后，Spring容器会自动扫描注解@Transactional的方法和类。<code>SpringBoot默认已开启</code>，可不用显式注解。<code>要使用事务，我们只需要在需要事务的类或方法上使用@Transactional 注解即可</code>，当注解在类上的时候意味着此类的所有public方法都是开启事务的。<code>被注解的方法都成为一个事务整体，同一个事务内共享一个数据库连接，所有操作同时发生。如果在事务内部执行过程中发生了异常，则事务整体会自动进行回滚。</code></p><h2 id="三大核心类" tabindex="-1">三大核心类 <a class="header-anchor" href="#三大核心类" aria-label="Permalink to &quot;三大核心类&quot;">​</a></h2><p>Spring 中对事务的支持提供了三大基础设施，我们先来了解下。</p><ol><li>PlatformTransactionManager</li><li>TransactionDefinition</li><li>TransactionStatus</li></ol><p>这三个核心类是 Spring 处理事务的核心类。</p><h3 id="_1-platformtransactionmanager" tabindex="-1">1 PlatformTransactionManager <a class="header-anchor" href="#_1-platformtransactionmanager" aria-label="Permalink to &quot;1 PlatformTransactionManager&quot;">​</a></h3><p>PlatformTransactionManager 是事务处理的核心，它有诸多的实现类，如下：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212081240164.png" alt="image-20221208124035002" style="zoom:67%;"><p>PlatformTransactionManager 的定义如下：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">interface</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">PlatformTransactionManager</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;"> 	</span><span style="color:#C792EA;">TransactionStatus</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">getTransaction</span><span style="color:#89DDFF;">(@</span><span style="color:#C792EA;">Nullable</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">TransactionDefinition</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">definition</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">	</span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">commit</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">TransactionStatus</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">status</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">TransactionException</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;"> 	</span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">rollback</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">TransactionStatus</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">status</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">TransactionException</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>可以看到 <code>PlatformTransactionManager</code> 中定义了基本的事务操作方法，这些事务操作方法都是平台无关的，具体的实现都是由不同的子类来实现的。</p><p>这就像 JDBC 一样，SUN 公司制定标准，其他数据库厂商提供具体的实现。这么做的好处就是我们 Java 程序员只需要掌握好这套标准即可，不用去管接口的具体实现。以 <code>PlatformTransactionManager</code> 为例，它有众多实现，如果你使用的是 JDBC 那么可以将 <code>DataSourceTransactionManager</code> 作为事务管理器；如果你使用的是 Hibernate，那么可以将 <code>HibernateTransactionManager</code> 作为事务管理器；如果你使用的是 JPA，那么可以将 <code>JpaTransactionManager</code> 作为事务管理器。<code>DataSourceTransactionManager</code>、<code>HibernateTransactionManager</code> 以及 <code>JpaTransactionManager</code> 都是 <code>PlatformTransactionManager</code> 的具体实现，但是我们并不需要掌握这些具体实现类的用法，我们只需要掌握好 <code>PlatformTransactionManager</code> 的用法即可。</p><p><code>PlatformTransactionManager</code> 中主要有如下三个方法：</p><p><strong>1.getTransaction()</strong></p><blockquote><p>getTransaction() 是根据传入的 TransactionDefinition 获取一个事务对象，TransactionDefinition 中定义了一些事务的基本规则，例如传播性、隔离级别等。</p></blockquote><p><strong>2.commit()</strong></p><blockquote><p>commit() 方法用来提交事务。</p></blockquote><p><strong>3.rollback()</strong></p><blockquote><p>rollback() 方法用来回滚事务。</p></blockquote><h3 id="_2-transactiondefinition" tabindex="-1">2 TransactionDefinition <a class="header-anchor" href="#_2-transactiondefinition" aria-label="Permalink to &quot;2 TransactionDefinition&quot;">​</a></h3><p><code>TransactionDefinition</code> 用来描述事务的具体规则，也称作事务的属性。事务有哪些属性呢？看下图：</p><p>可以看到，主要是五种属性：</p><ol><li>隔离性</li><li>传播性</li><li>回滚规则</li><li>超时时间</li><li>是否只读</li></ol><p>这五种属性接下来松哥会和大家详细介绍。</p><p><code>TransactionDefinition</code> 类中的方法如下：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/GvtDGKK4uYmGWzE7sFQZmh74H5Z4j2e763Ff20fkYqVcmthuUJVibIGkRuxwBCHicLw9GehM1Sh6b4j5gOD3menQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>可以看到一共有五个方法：</p><ol><li>getIsolationLevel()，获取事务的隔离级别</li><li>getName()，获取事务的名称</li><li>getPropagationBehavior()，获取事务的传播性</li><li>getTimeout()，获取事务的超时时间</li><li>isReadOnly()，获取事务是否是只读事务</li></ol><p>TransactionDefinition 也有诸多的实现类，如下：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/GvtDGKK4uYmGWzE7sFQZmh74H5Z4j2e7p77Phbh3fkaTY2pSBTRl8ghdcD5YCA2rVS51W5VdM3gQE7xgo6Y1Fw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>如果开发者使用了编程式事务的话，直接使用 <code>DefaultTransactionDefinition</code> 即可。</p><h3 id="_3-transactionstatus" tabindex="-1">3 TransactionStatus <a class="header-anchor" href="#_3-transactionstatus" aria-label="Permalink to &quot;3 TransactionStatus&quot;">​</a></h3><p>TransactionStatus 可以直接理解为事务本身，该接口源码如下：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">interface</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">TransactionStatus</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">extends</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">SavepointManager</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Flushable</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;"> 	</span><span style="color:#C792EA;">boolean</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">isNewTransaction</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;"> 	</span><span style="color:#C792EA;">boolean</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">hasSavepoint</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;"> 	</span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">setRollbackOnly</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;"> 	</span><span style="color:#C792EA;">boolean</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">isRollbackOnly</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">	</span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">flush</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">	</span><span style="color:#C792EA;">boolean</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">isCompleted</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><ol><li>isNewTransaction() 方法获取当前事务是否是一个新事务。</li><li>hasSavepoint() 方法判断是否存在 savePoint()。</li><li>setRollbackOnly() 方法设置事务必须回滚。</li><li>isRollbackOnly() 方法获取事务只能回滚。</li><li>flush() 方法将底层会话中的修改刷新到数据库，一般用于 Hibernate/JPA 的会话，如 JDBC 类型的事务无任何影响</li><li>isCompleted() 方法用来获取是一个事务是否结束。</li></ol><p><strong>这就是 Spring 中支持事务的三大基础设施。</strong></p><h2 id="transactional" tabindex="-1">@Transactional <a class="header-anchor" href="#transactional" aria-label="Permalink to &quot;@Transactional&quot;">​</a></h2><p>@Transactional 可以作用在<code>接口</code>、<code>类</code>、<code>类方法</code>。</p><ul><li><strong>作用于类</strong>：当把@Transactional 注解放在类上时，表示所有该类的<code>public</code>方法都配置相同的事务属性信息。</li><li><strong>作用于方法</strong>：当类配置了@Transactional，方法也配置了@Transactional，方法的事务会覆盖类的事务配置信息。</li><li><strong>作用于接口</strong>：不推荐这种使用方法，因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理，将会导致@Transactional注解失效</li><li>==建议写在实现类或实现类的方法上==</li></ul><h3 id="rollbackfor属性" tabindex="-1">rollbackFor属性 <a class="header-anchor" href="#rollbackfor属性" aria-label="Permalink to &quot;rollbackFor属性&quot;">​</a></h3><p><code>@Transactional</code>只能回滚<code>RuntimeException</code>和<code>RuntimeException</code>下面的子类抛出的异常 不能回滚<code>Exception</code>异常</p><p>如果需要支持回滚<code>Exception</code>异常请用<code>@Transactional(rollbackFor = Exception.class)</code></p><p>这里如果是增删改的时候我建议大家都使用<code>@Transactional(rollbackFor = Exception.class)</code></p><h2 id="transactional隔离级别" tabindex="-1">@Transactional隔离级别 <a class="header-anchor" href="#transactional隔离级别" aria-label="Permalink to &quot;@Transactional隔离级别&quot;">​</a></h2><p>事务之间有不同的隔离级别，不同的隔离级别可以解决并发事务所带来的问题。在SpringBoot中，事务的隔离级别定义为@Transactional 注解中的属性</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">isolation</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> Isolation</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">DEFAULT</span><span style="color:#89DDFF;">)</span></span></code></pre></div><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">//默认的隔离级别，使用当前数据库的隔离级别。会是后面四种隔离级别的其中一种</span></span>
<span class="line"><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> ISOLATION_DEFAULT </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">-</span><span style="color:#F78C6C;">1</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> ISOLATION_READ_UNCOMMITTED </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#89DDFF;">;</span><span style="color:#676E95;font-style:italic;">//读未提交;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> ISOLATION_READ_COMMITTED </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">2</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">//读已提交；</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> ISOLATION_REPEATABLE_READ </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">4</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">//重复读；</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> ISOLATION_SERIALIZABLE </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">8</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">//串行化</span></span></code></pre></div><ul><li>DEFAULT ：默认值（也是SpringBoot的隔离级别默认值），表示使用底层数据库的默认隔离级别。大部分数据库为READ_COMMITTED(MySql默认隔离级别为REPEATABLE_READ)</li><li>READ_UNCOMMITTED ：该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读，因此很少使用该隔离级别。 （解决了丢失修改，但会出现脏读，不可重复读和幻读）</li><li>READ_COMMITTED ：该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读，这也是大多数情况下的推荐值。 （解决了更新丢失，脏读。但会出现不可重复读，幻读）</li><li>REPEATABLE_READ ：该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询，并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询，这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。 （解决了更新丢失，脏读，不可重复度，同时有人指出在 mysql 的 innodb 引擎上，配合 mvvc + gap 锁，已经解决了幻读问题）</li><li>SERIALIZABLE ：所有的事务依次逐个执行，这样事务之间就完全不可能产生干扰，也就是说，该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 （解决所有问题，串行执行）</li></ul><h2 id="transactional角色" tabindex="-1">@Transactional角色 <a class="header-anchor" href="#transactional角色" aria-label="Permalink to &quot;@Transactional角色&quot;">​</a></h2><p>这节中我们重点要理解两个概念，分别是<code>事务管理员</code>和<code>事务协调员</code>。</p><ol><li>未开启Spring事务之前:</li></ol><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.11/202206101720610.png" alt="1630248794837" style="zoom:80%;"><ul><li>AccountDao的outMoney因为是修改操作，会开启一个事务T1</li><li>AccountDao的inMoney因为是修改操作，会开启一个事务T2</li><li>AccountService的transfer没有事务， <ul><li>运行过程中如果没有抛出异常，则T1和T2都正常提交，数据正确</li><li>如果在两个方法中间抛出异常，T1因为执行成功提交事务，T2因为抛异常不会被执行</li><li>就会导致数据出现错误</li></ul></li></ul><ol start="2"><li>开启Spring的事务管理后</li></ol><p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.11/202206101720616.png" alt="1630249111055"></p><ul><li>transfer上添加了@Transactional注解，在该方法上就会有一个事务T</li><li>AccountDao的outMoney方法的事务T1加入到transfer的事务T中</li><li>AccountDao的inMoney方法的事务T2加入到transfer的事务T中</li><li>这样就保证他们在同一个事务中，当业务层中出现异常，整个事务就会回滚，保证数据的准确性。</li></ul><p>通过上面例子的分析，我们就可以得到如下概念:</p><ul><li>事务管理员：发起事务方，在Spring中通常指代业务层开启事务的方法</li><li>事务协调员：加入事务方，在Spring中通常指代数据层方法，也可以是业务层方法</li></ul><h2 id="transactional属性" tabindex="-1">@Transactional属性 <a class="header-anchor" href="#transactional属性" aria-label="Permalink to &quot;@Transactional属性&quot;">​</a></h2><p>上一节我们介绍了两个概念，事务的管理员和事务的协同员，对于这两个概念具体做什么的，我们待会通过案例来使用下。除了这两个概念，还有就是事务的其他相关配置都有哪些，就是我们接下来要学习的内容。</p><p>在这一节中，我们主要学习三部分内容<code>事务配置</code>、<code>转账业务追加日志</code>、<code>事务传播行为</code>。</p><h3 id="_1-事务配置" tabindex="-1">1 事务配置 <a class="header-anchor" href="#_1-事务配置" aria-label="Permalink to &quot;1 事务配置&quot;">​</a></h3><p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.11/202206101720614.png" alt="1630250069844"></p><p>上面这些属性都可以在<code>@Transactional</code>注解的参数上进行设置。</p><ul><li><p>readOnly：true只读事务，false读写事务，增删改要设为false,查询设为true。</p></li><li><p>timeout:设置超时时间单位秒，在多长时间之内事务没有提交成功就自动回滚，-1表示不设置超时时间。</p></li><li><p>rollbackFor:当出现指定异常进行事务回滚</p></li><li><p>noRollbackFor:当出现指定异常不进行事务回滚</p><ul><li><p>思考:出现异常事务会自动回滚，这个是我们之前就已经知道的</p></li><li><p>noRollbackFor是设定对于指定的异常不回滚，这个好理解</p></li><li><p>rollbackFor是指定回滚异常，对于异常事务不应该都回滚么，为什么还要指定?</p><ul><li><p>这块需要更正一个知识点，并不是所有的异常都会回滚事务，比如下面的代码就不会回滚</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">interface</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">AccountService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">    /**</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">     * 转账操作</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">     * </span><span style="color:#F78C6C;font-style:italic;">@param</span><span style="color:#676E95;font-style:italic;"> </span><span style="color:#A6ACCD;font-style:italic;">out</span><span style="color:#676E95;font-style:italic;"> 传出方</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">     * </span><span style="color:#F78C6C;font-style:italic;">@param</span><span style="color:#676E95;font-style:italic;"> </span><span style="color:#A6ACCD;font-style:italic;">in</span><span style="color:#676E95;font-style:italic;"> 转入方</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">     * </span><span style="color:#F78C6C;font-style:italic;">@param</span><span style="color:#676E95;font-style:italic;"> </span><span style="color:#A6ACCD;font-style:italic;">money</span><span style="color:#676E95;font-style:italic;"> 金额</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">     */</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//配置当前接口方法具有事务</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">transfer</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">out</span><span style="color:#89DDFF;">,</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">in</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">,</span><span style="color:#C792EA;">Double</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">money</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">IOException</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">AccountServiceImpl</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">AccountService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">AccountDao</span><span style="color:#A6ACCD;"> accountDao</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">	</span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">transfer</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">out</span><span style="color:#89DDFF;">,</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">in</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">,</span><span style="color:#C792EA;">Double</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">money</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">IOException</span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        accountDao</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">outMoney</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">money</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//int i = 1/0; //这个异常事务会回滚</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#89DDFF;">(true){</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">throw</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">IOException</span><span style="color:#89DDFF;">();</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">//这个异常事务就不会回滚</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        accountDao</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">inMoney</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">in</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">money</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div></li></ul></li></ul></li><li><p>出现这个问题的原因是，Spring的事务只会对<code>Error异常</code>和<code>RuntimeException异常</code>及其子类进行事务回顾，其他的异常类型是不会回滚的，对应IOException不符合上述条件所以不回滚</p><ul><li><p>此时就可以使用rollbackFor属性来设置出现IOException异常不回滚</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">AccountServiceImpl</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">AccountService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">AccountDao</span><span style="color:#A6ACCD;"> accountDao</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">	 </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">rollbackFor</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">IOException</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">})</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">transfer</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">out</span><span style="color:#89DDFF;">,</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">in</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">,</span><span style="color:#C792EA;">Double</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">money</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">IOException</span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        accountDao</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">outMoney</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">money</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//int i = 1/0; //这个异常事务会回滚</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#89DDFF;">(true){</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">throw</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">IOException</span><span style="color:#89DDFF;">();</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">//这个异常事务就不会回滚</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        accountDao</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">inMoney</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">in</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">money</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div></li></ul></li><li><p>rollbackForClassName等同于rollbackFor,只不过属性为异常的类全名字符串</p></li><li><p>noRollbackForClassName等同于noRollbackFor，只不过属性为异常的类全名字符串</p></li><li><p>isolation设置事务的隔离级别</p><ul><li>DEFAULT :默认隔离级别, 会采用数据库的隔离级别</li><li>READ_UNCOMMITTED : 读未提交</li><li>READ_COMMITTED : 读已提交</li><li>REPEATABLE_READ : 重复读取</li><li>SERIALIZABLE: 串行化</li></ul></li></ul><p>介绍完上述属性后，还有最后一个事务的传播行为，为了讲解该属性的设置，我们需要完成下面的案例。</p><h3 id="_2-转账业务追加日志案例" tabindex="-1">2 转账业务追加日志案例 <a class="header-anchor" href="#_2-转账业务追加日志案例" aria-label="Permalink to &quot;2 转账业务追加日志案例&quot;">​</a></h3><h5 id="_1-需求分析" tabindex="-1">1 需求分析 <a class="header-anchor" href="#_1-需求分析" aria-label="Permalink to &quot;1 需求分析&quot;">​</a></h5><p>在前面的转案例的基础上添加新的需求，完成转账后记录日志。</p><ul><li>需求：实现任意两个账户间转账操作，并对每次转账操作在数据库进行留痕</li><li>需求微缩：A账户减钱，B账户加钱，数据库记录日志</li></ul><p>基于上述的业务需求，我们来分析下该如何实现:</p><p>①：基于转账操作案例添加日志模块，实现数据库中记录日志</p><p>②：业务层转账操作（transfer），调用减钱、加钱与记录日志功能</p><p>需要注意一点就是，我们这个案例的预期效果为:</p><p>==无论转账操作是否成功，均进行转账操作的日志留痕==</p><h5 id="_2-环境准备" tabindex="-1">2 环境准备 <a class="header-anchor" href="#_2-环境准备" aria-label="Permalink to &quot;2 环境准备&quot;">​</a></h5><p>该环境是基于转账环境来完成的，所以环境的准备可以参考<code>6.1.3的环境搭建步骤</code>，在其基础上，我们继续往下写</p><h6 id="步骤1-创建日志表" tabindex="-1">步骤1:创建日志表 <a class="header-anchor" href="#步骤1-创建日志表" aria-label="Permalink to &quot;步骤1:创建日志表&quot;">​</a></h6><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">create</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">table</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">tbl_log</span><span style="color:#A6ACCD;">(</span></span>
<span class="line"><span style="color:#A6ACCD;">   id </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">primary key</span><span style="color:#A6ACCD;"> auto_increment,</span></span>
<span class="line"><span style="color:#A6ACCD;">   info </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">255</span><span style="color:#A6ACCD;">),</span></span>
<span class="line"><span style="color:#A6ACCD;">   createDate </span><span style="color:#F78C6C;">datetime</span></span>
<span class="line"><span style="color:#A6ACCD;">)</span></span></code></pre></div><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Data</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">log</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Integer</span><span style="color:#A6ACCD;"> id</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> info</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">LocalDateTime</span><span style="color:#A6ACCD;"> createDate</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h6 id="步骤2-添加logmapper接口" tabindex="-1">步骤2:添加LogMapper接口 <a class="header-anchor" href="#步骤2-添加logmapper接口" aria-label="Permalink to &quot;步骤2:添加LogMapper接口&quot;">​</a></h6><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Mapper</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">interface</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">logMapper</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">extends</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">BaseMapper</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">log</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Insert</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">insert into tbl_log (info,createDate) values(#{info},now())</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">log</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">info</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h6 id="步骤3-添加logservice接口与实现类" tabindex="-1">步骤3:添加LogService接口与实现类 <a class="header-anchor" href="#步骤3-添加logservice接口与实现类" aria-label="Permalink to &quot;步骤3:添加LogService接口与实现类&quot;">​</a></h6><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">interface</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">LogService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">log</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">out</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">in</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Double</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">money</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">com</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">it</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">mapper</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">logMapper</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">stereotype</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Service</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">transaction</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Propagation</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">transaction</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">javax</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Resource</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">LogServiceImpl</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">LogService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Resource</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> logMapper logMapper</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 事务传播</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">propagation</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> Propagation</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">REQUIRES_NEW</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">log</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">out</span><span style="color:#89DDFF;">,</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">in</span><span style="color:#89DDFF;">,</span><span style="color:#C792EA;">Double</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">money</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        logMapper</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">log</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">转账操作由</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">+</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">到</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;">in</span><span style="color:#89DDFF;">+</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">,金额：</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;">money</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h6 id="步骤4-在转账的业务中添加记录日志" tabindex="-1">步骤4:在转账的业务中添加记录日志 <a class="header-anchor" href="#步骤4-在转账的业务中添加记录日志" aria-label="Permalink to &quot;步骤4:在转账的业务中添加记录日志&quot;">​</a></h6><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">com</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">it</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">mapper</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">accountMapper</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">stereotype</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Service</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">transaction</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">javax</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Resource</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">AccountServiceImpl</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">AccountService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Resource</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> accountMapper accountMapper</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Resource</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">LogService</span><span style="color:#A6ACCD;"> logService</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//out，in是name</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//不加该注解，那么依旧是只有一个改变，否则就是两个都不变</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">rollbackFor</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> Exception</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">transfer</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">out</span><span style="color:#89DDFF;">,</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">in</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">,</span><span style="color:#C792EA;">Double</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">money</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//注意：不能catch异常，不然事务就不能执行了</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">try</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            accountMapper</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">outMoney</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">money</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">//抛出异常，测试事务</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#89DDFF;">/</span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">            accountMapper</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">inMoney</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">in</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">money</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">finally</span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            logService</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">log</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">in</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">money</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h6 id="步骤5-运行程序" tabindex="-1">步骤5:运行程序 <a class="header-anchor" href="#步骤5-运行程序" aria-label="Permalink to &quot;步骤5:运行程序&quot;">​</a></h6><ul><li><p>当程序正常运行，tbl_account表中转账成功，tbl_log表中日志记录成功</p></li><li><p>当转账业务之间出现异常(int i =1/0),转账失败，tbl_account成功回滚，但是tbl_log表未添加数据</p></li><li><p>这个结果和我们想要的不一样，什么原因?该如何解决?</p></li><li><p>失败原因:日志的记录与转账操作隶属同一个事务，同成功同失败</p></li><li><p>最终效果:无论转账操作是否成功，日志必须保留</p></li></ul><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Resource</span></span>
<span class="line"><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">AccountService</span><span style="color:#A6ACCD;"> accountService</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Test</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">testTransfer</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//Tom -&gt; Jack 转账100元</span></span>
<span class="line"><span style="color:#A6ACCD;">    accountService</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">transfer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Tom</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Jerry</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">100.0</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.11/202206101944351.png" alt="image-20220610194459273" style="zoom:80%;"><h3 id="_3-事务传播行为" tabindex="-1">3 事务传播行为 <a class="header-anchor" href="#_3-事务传播行为" aria-label="Permalink to &quot;3 事务传播行为&quot;">​</a></h3><p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.11/202206101720197.png" alt="1630253779575"></p><p>对于上述案例的分析:</p><ul><li>log方法、inMoney方法和outMoney方法都属于增删改，分别有事务T1,T2,T3</li><li>transfer因为加了@Transactional注解，也开启了事务T</li><li>前面我们讲过Spring事务会把T1,T2,T3都加入到事务T中</li><li>所以当转账失败后，所有的事务都回滚，导致日志没有记录下来</li><li>这和我们的需求不符，<code>这个时候我们就想能不能让log方法单独是一个事务呢?</code></li></ul><p>要想解决这个问题，就需要用到事务传播行为，所谓的事务传播行为指的是:</p><p>事务传播行为：事务协调员对事务管理员所携带事务的处理态度。</p><p>具体如何解决，就需要用到之前我们没有说的<code>propagation属性</code>。</p><h5 id="_1-修改logservice改变事务的传播行为" tabindex="-1">1.修改logService改变事务的传播行为 <a class="header-anchor" href="#_1-修改logservice改变事务的传播行为" aria-label="Permalink to &quot;1.修改logService改变事务的传播行为&quot;">​</a></h5><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">LogServiceImpl</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">LogService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">LogDao</span><span style="color:#A6ACCD;"> logDao</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">	</span><span style="color:#676E95;font-style:italic;">//propagation设置事务属性：传播行为设置为当前操作需要新事务</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">propagation</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> Propagation</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">REQUIRES_NEW</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">log</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">out</span><span style="color:#89DDFF;">,</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">in</span><span style="color:#89DDFF;">,</span><span style="color:#C792EA;">Double</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">money</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        logDao</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">log</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">转账操作由</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">+</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">到</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;">in</span><span style="color:#89DDFF;">+</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">,金额：</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;">money</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>运行后，就能实现我们想要的结果，不管转账是否成功，都会记录日志。</p><h5 id="_2-事务传播行为的可选值" tabindex="-1">2.事务传播行为的可选值 <a class="header-anchor" href="#_2-事务传播行为的可选值" aria-label="Permalink to &quot;2.事务传播行为的可选值&quot;">​</a></h5><p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.11/202206101720202.png" alt="1630254257628"></p><p>对于我们开发实际中使用的话，因为默认值需要事务是常态的。根据开发过程选择其他的就可以了，例如案例中需要新事务就需要手工配置。其实入账和出账操作上也有事务，采用的就是默认值。</p><h1 id="spring-事务失效的-8-种场景" tabindex="-1">Spring 事务失效的 8 种场景！ <a class="header-anchor" href="#spring-事务失效的-8-种场景" aria-label="Permalink to &quot;Spring 事务失效的 8 种场景！&quot;">​</a></h1><p>对于从事java开发工作的同学来说，spring的事务肯定再熟悉不过了。</p><p>在某些业务场景下，如果一个请求中，需要同时写入多张表的数据。为了保证操作的原子性（要么同时成功，要么同时失败），避免数据不一致的情况，我们一般都会用到spring事务。</p><p>确实，spring事务用起来贼爽，就用一个简单的注解：<code>@Transactional</code>，就能轻松搞定事务。我猜大部分小伙伴也是这样用的，而且一直用一直爽。</p><p>但如果你使用不当，它也会坑你于无形。</p><p>今天我们就一起聊聊，事务失效的一些场景，说不定你已经中招了。不信，让我们一起看看。</p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/uL371281oDFI5ibhP1TXOMnqQtJhfb3XCnTbgmpiab2LDA8VVCmg2jMUoeJd70gAJsj7vL2IB0icYxsbsvnKIu9LQ/640?wx_fmt=jpeg&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片" style="zoom:67%;"><h2 id="一-事务不生效" tabindex="-1">一 事务不生效 <a class="header-anchor" href="#一-事务不生效" aria-label="Permalink to &quot;一 事务不生效&quot;">​</a></h2><h3 id="_1-访问权限问题" tabindex="-1">1.访问权限问题 <a class="header-anchor" href="#_1-访问权限问题" aria-label="Permalink to &quot;1.访问权限问题&quot;">​</a></h3><p>众所周知，java的访问权限主要有四种：private、default、protected、public，它们的权限从左到右，依次变大。</p><p>但如果我们在开发过程中，把有某些事务方法，定义了错误的访问权限，就会导致事务功能出问题，例如：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">UserService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">UserModel</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">userModel</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">saveData</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">updateData</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>我们可以看到add方法的访问权限被定义成了<code>private</code>，这样会导致事务失效，spring要求被代理方法必须是<code>public</code>的。说白了，在<code>AbstractFallbackTransactionAttributeSource</code>类的<code>computeTransactionAttribute</code>方法中有个判断，如果目标方法不是public，则<code>TransactionAttribute</code>返回null，即不支持事务。</p><p>也就是说，如果我们自定义的事务方法（即目标方法），它的访问权限不是<code>public</code>，而是private、default或protected的话，spring则不会提供事务功能。</p><h3 id="_2-方法用final修饰" tabindex="-1">2. 方法用final修饰 <a class="header-anchor" href="#_2-方法用final修饰" aria-label="Permalink to &quot;2. 方法用final修饰&quot;">​</a></h3><p>有时候，某个方法不想被子类重新，这时可以将该方法定义成final的。普通方法这样定义是没问题的，但如果将事务方法定义成final，例如：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">UserService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">final</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">UserModel</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">userModel</span><span style="color:#89DDFF;">){</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#82AAFF;">saveData</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#82AAFF;">updateData</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>我们可以看到add方法被定义成了<code>final</code>的，这样会导致事务失效。</p><p>为什么？</p><p>如果你看过spring事务的源码，可能会知道spring事务底层使用了aop，也就是通过jdk动态代理或者cglib，帮我们生成了代理类，在代理类中实现的事务功能。</p><p>但如果某个方法用final修饰了，那么在它的代理类中，就无法重写该方法，而添加事务功能。</p><blockquote><p>注意：如果某个方法是static的，同样无法通过动态代理，变成事务方法。</p></blockquote><h3 id="_3-方法内部调用" tabindex="-1">3.方法内部调用 <a class="header-anchor" href="#_3-方法内部调用" aria-label="Permalink to &quot;3.方法内部调用&quot;">​</a></h3><p>有时候我们需要在某个Service类的某个方法中，调用另外一个事务方法，比如：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">UserService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">UserMapper</span><span style="color:#A6ACCD;"> userMapper</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">UserModel</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">userModel</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        userMapper</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">insertUser</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#82AAFF;">updateStatus</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">updateStatus</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">UserModel</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">userModel</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#82AAFF;">doSameThing</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>我们看到在事务方法add中，直接调用事务方法updateStatus。从前面介绍的内容可以知道，updateStatus方法拥有事务的能力是因为spring aop生成代理了对象，但是这种方法直接调用了this对象的方法，所以updateStatus方法不会生成事务。</p><p>由此可见，在同一个类中的方法直接内部调用，会导致事务失效。</p><p>那么问题来了，如果有些场景，确实想在同一个类的某个方法中，调用它自己的另外一个方法，该怎么办呢？</p><h4 id="_3-1-新加一个service方法" tabindex="-1">3.1 新加一个Service方法 <a class="header-anchor" href="#_3-1-新加一个service方法" aria-label="Permalink to &quot;3.1 新加一个Service方法&quot;">​</a></h4><p>这个方法非常简单，只需要新加一个Service方法，把@Transactional注解加到新Service方法上，把需要事务执行的代码移到新方法中。具体代码如下：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Servcie</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">ServiceA</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">   prvate </span><span style="color:#C792EA;">ServiceB</span><span style="color:#A6ACCD;"> serviceB</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">queryData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">queryData2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">         serviceB</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">doSave</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">user</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Servcie</span></span>
<span class="line"><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">ServiceB</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">rollbackFor</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">Exception</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">doSave</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#82AAFF;">addData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#82AAFF;">updateData2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">}</span></span></code></pre></div><h4 id="_3-2-在该service类中注入自己" tabindex="-1">3.2 在该Service类中注入自己 <a class="header-anchor" href="#_3-2-在该service类中注入自己" aria-label="Permalink to &quot;3.2 在该Service类中注入自己&quot;">​</a></h4><p>如果不想再新加一个Service类，在该Service类中注入自己也是一种选择。具体代码如下：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Servcie</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">ServiceA</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">   prvate </span><span style="color:#C792EA;">ServiceA</span><span style="color:#A6ACCD;"> serviceA</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">queryData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">queryData2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">         serviceA</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">doSave</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">user</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">rollbackFor</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">Exception</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">doSave</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#82AAFF;">addData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#82AAFF;">updateData2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">}</span></span></code></pre></div><p>可能有些人可能会有这样的疑问：这种做法会不会出现循环依赖问题？</p><p>答案：不会。</p><p>其实spring ioc内部的三级缓存保证了它，不会出现循环依赖问题。但有些坑，如果你想进一步了解循环依赖问题，可以看看我之前文章《<a href="https://mp.weixin.qq.com/s?__biz=MzUxODkzNTQ3Nw==&amp;mid=2247485600&amp;idx=1&amp;sn=0c49b94e7fbd35c88c4470e936023e3e&amp;chksm=f9800e7acef7876ca05ab45ce9420ea140f188e84153f23d0af9d044f475458ad38d49a6546a&amp;token=1641046204&amp;lang=zh_CN&amp;scene=21#wechat_redirect" target="_blank" rel="noreferrer">spring：我是如何解决循环依赖的？</a>》。</p><h4 id="_3-3-通过aopcontent类" tabindex="-1">3.3 通过AopContent类 <a class="header-anchor" href="#_3-3-通过aopcontent类" aria-label="Permalink to &quot;3.3 通过AopContent类&quot;">​</a></h4><p>在该Service类中使用AopContext.currentProxy()获取代理对象</p><p>上面的方法2确实可以解决问题，但是代码看起来并不直观，还可以通过在该Service类中使用AOPProxy获取代理对象，实现相同的功能。具体代码如下：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Servcie</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">ServiceA</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">queryData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">queryData2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#89DDFF;">((</span><span style="color:#A6ACCD;">ServiceA</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;">AopContext</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">currentProxy</span><span style="color:#89DDFF;">()).</span><span style="color:#82AAFF;">doSave</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">user</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">rollbackFor</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">Exception</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">doSave</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#82AAFF;">addData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#82AAFF;">updateData2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">}</span></span></code></pre></div><h3 id="_4-未被spring管理" tabindex="-1">4.未被spring管理 <a class="header-anchor" href="#_4-未被spring管理" aria-label="Permalink to &quot;4.未被spring管理&quot;">​</a></h3><p>在我们平时开发过程中，有个细节很容易被忽略。即使用spring事务的前提是：对象要被spring管理，需要创建bean实例。</p><p>通常情况下，我们通过@Controller、@Service、@Component、@Repository等注解，可以自动实现bean实例化和依赖注入的功能。</p><p>当然创建bean实例的方法还有很多，有兴趣的小伙伴可以看看我之前写的另一篇文章《<a href="https://mp.weixin.qq.com/s?__biz=MzUxODkzNTQ3Nw==&amp;mid=2247488466&amp;idx=1&amp;sn=1e63e6991b5fb47e067d2edf055981d3&amp;chksm=f9801508cef79c1ea208906ceef09593a9f594b3926478eb65b7ef64e0311f13ac5aaf4135ce&amp;token=1641046204&amp;lang=zh_CN&amp;scene=21#wechat_redirect" target="_blank" rel="noreferrer">@Autowired的这些骚操作，你都知道吗？</a>》</p><p>如果有一天，你匆匆忙忙的开发了一个Service类，但忘了加@Service注解，比如：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">//@Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">UserService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">UserModel</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">userModel</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">saveData</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">updateData</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">    </span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>从上面的例子，我们可以看到UserService类没有加<code>@Service</code>注解，那么该类不会交给spring管理，所以它的add方法也不会生成事务。</p><h3 id="_5-多线程调用" tabindex="-1">5.多线程调用 <a class="header-anchor" href="#_5-多线程调用" aria-label="Permalink to &quot;5.多线程调用&quot;">​</a></h3><p>在实际项目开发中，多线程的使用场景还是挺多的。如果spring事务用在多线程场景中，会有问题吗？</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Slf4j</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">UserService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">UserMapper</span><span style="color:#A6ACCD;"> userMapper</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">RoleService</span><span style="color:#A6ACCD;"> roleService</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">UserModel</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">userModel</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        userMapper</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">insertUser</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Thread</span><span style="color:#89DDFF;">(()</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">-&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            roleService</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">doOtherThing</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}).</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">RoleService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">doOtherThing</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">保存role表数据</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>从上面的例子中，我们可以看到事务方法add中，调用了事务方法doOtherThing，但是事务方法doOtherThing是在另外一个线程中调用的。</p><p>这样会导致两个方法不在同一个线程中，获取到的数据库连接不一样，从而是两个不同的事务。如果想doOtherThing方法中抛了异常，add方法也回滚是不可能的。</p><p>如果看过spring事务源码的朋友，可能会知道spring的事务是通过数据库连接来实现的。当前线程中保存了一个map，key是数据源，value是数据库连接。</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">final</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">ThreadLocal</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">Map</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">Object</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#89DDFF;">&gt;&gt;</span><span style="color:#A6ACCD;"> resources </span><span style="color:#89DDFF;">=</span></span>
<span class="line"><span style="color:#A6ACCD;">               </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">NamedThreadLocal</span><span style="color:#89DDFF;">&lt;&gt;(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Transactional resources</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span></code></pre></div><p>我们说的同一个事务，其实是指同一个数据库连接，只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程，拿到的数据库连接肯定是不一样的，所以是不同的事务。</p><h3 id="_6-表不支持事务" tabindex="-1">6.表不支持事务 <a class="header-anchor" href="#_6-表不支持事务" aria-label="Permalink to &quot;6.表不支持事务&quot;">​</a></h3><p>周所周知，在mysql5之前，默认的数据库引擎是<code>myisam</code>。</p><p>它的好处就不用多说了：索引文件和数据文件是分开存储的，对于查多写少的单表操作，性能比innodb更好。</p><p>有些老项目中，可能还在用它。</p><p>在创建表的时候，只需要把<code>ENGINE</code>参数设置成<code>MyISAM</code>即可：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">CREATE</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">TABLE</span><span style="color:#A6ACCD;"> `</span><span style="color:#82AAFF;">category</span><span style="color:#A6ACCD;">` (</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">bigint</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> AUTO_INCREMENT,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">one_category</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">20</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">COLLATE</span><span style="color:#A6ACCD;"> utf8mb4_bin </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">two_category</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">20</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">COLLATE</span><span style="color:#A6ACCD;"> utf8mb4_bin </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">three_category</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">20</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">COLLATE</span><span style="color:#A6ACCD;"> utf8mb4_bin </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">four_category</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">20</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">COLLATE</span><span style="color:#A6ACCD;"> utf8mb4_bin </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#C792EA;">PRIMARY KEY</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">) ENGINE</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">MyISAM AUTO_INCREMENT</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">4</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> CHARSET</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">utf8mb4 </span><span style="color:#C792EA;">COLLATE</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">utf8mb4_bin</span></span></code></pre></div><p>myisam好用，但有个很致命的问题是：<code>不支持事务</code>。</p><p>如果只是单表操作还好，不会出现太大的问题。但如果需要跨多张表操作，由于其不支持事务，数据极有可能会出现不完整的情况。</p><p>此外，myisam还不支持行锁和外键。</p><p>所以在实际业务场景中，myisam使用的并不多。在mysql5以后，myisam已经逐渐退出了历史的舞台，取而代之的是innodb。</p><blockquote><p>有时候我们在开发的过程中，发现某张表的事务一直都没有生效，那不一定是spring事务的锅，最好确认一下你使用的那张表，是否支持事务。</p></blockquote><h3 id="_7-未开启事务" tabindex="-1">7.未开启事务 <a class="header-anchor" href="#_7-未开启事务" aria-label="Permalink to &quot;7.未开启事务&quot;">​</a></h3><p>有时候，事务没有生效的根本原因是没有开启事务。</p><p>你看到这句话可能会觉得好笑。</p><p>开启事务不是一个项目中，最最最基本的功能吗？</p><p>为什么还会没有开启事务？</p><p>没错，如果项目已经搭建好了，事务功能肯定是有的。</p><p>但如果你是在搭建项目demo的时候，只有一张表，而这张表的事务没有生效。那么会是什么原因造成的呢？</p><p>当然原因有很多，但没有开启事务，这个原因极其容易被忽略。</p><p>如果你使用的是springboot项目，那么你很幸运。因为springboot通过<code>DataSourceTransactionManagerAutoConfiguration</code>类，已经默默的帮你开启了事务。</p><p>你所要做的事情很简单，只需要配置<code>spring.datasource</code>相关参数即可。</p><p>但如果你使用的还是传统的spring项目，则需要在applicationContext.xml文件中，手动配置事务相关参数。如果忘了配置，事务肯定是不会生效的。</p><p>具体配置如下信息：</p><div class="language-xml"><button title="Copy Code" class="copy"></button><span class="lang">xml</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">&lt;!-- 配置事务管理器 --&gt;</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">bean</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">class</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">org.springframework.jdbc.datasource.DataSourceTransactionManager</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">id</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">transactionManager</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">property</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">name</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">dataSource</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">ref</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">dataSource</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">&gt;&lt;/</span><span style="color:#F07178;">property</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">bean</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">tx</span><span style="color:#89DDFF;">:</span><span style="color:#F07178;">advice</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">id</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">advice</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">transaction-manager</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">transactionManager</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">tx</span><span style="color:#89DDFF;">:</span><span style="color:#F07178;">attributes</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">tx</span><span style="color:#89DDFF;">:</span><span style="color:#F07178;">method</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">name</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">*</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">propagation</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">REQUIRED</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">/&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">tx</span><span style="color:#89DDFF;">:</span><span style="color:#F07178;">attributes</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">tx</span><span style="color:#89DDFF;">:</span><span style="color:#F07178;">advice</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">&lt;!-- 用切点把事务切进去 --&gt;</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">aop</span><span style="color:#89DDFF;">:</span><span style="color:#F07178;">config</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">aop</span><span style="color:#89DDFF;">:</span><span style="color:#F07178;">pointcut</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">expression</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">execution(* com.susan.*.*(..))</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">id</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">pointcut</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">/&gt;</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">aop</span><span style="color:#89DDFF;">:</span><span style="color:#F07178;">advisor</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">advice-ref</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">advice</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">pointcut-ref</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">pointcut</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">/&gt;</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">aop</span><span style="color:#89DDFF;">:</span><span style="color:#F07178;">config</span><span style="color:#89DDFF;">&gt;</span></span></code></pre></div><p>默默的说一句，如果在pointcut标签中的切入点匹配规则，配错了的话，有些类的事务也不会生效。</p><h2 id="二-事务不回滚" tabindex="-1">二 事务不回滚 <a class="header-anchor" href="#二-事务不回滚" aria-label="Permalink to &quot;二 事务不回滚&quot;">​</a></h2><h3 id="_1-错误的传播特性" tabindex="-1">1.错误的传播特性 <a class="header-anchor" href="#_1-错误的传播特性" aria-label="Permalink to &quot;1.错误的传播特性&quot;">​</a></h3><p>其实，我们在使用<code>@Transactional</code>注解时，是可以指定<code>propagation</code>参数的。</p><p>该参数的作用是指定事务的传播特性，spring目前支持7种传播特性：</p><ul><li><code>REQUIRED</code> 如果当前上下文中存在事务，那么加入该事务，如果不存在事务，创建一个事务，这是默认的传播属性值。</li><li><code>SUPPORTS</code> 如果当前上下文存在事务，则支持事务加入事务，如果不存在事务，则使用非事务的方式执行。</li><li><code>MANDATORY</code> 如果当前上下文中存在事务，否则抛出异常。</li><li><code>REQUIRES_NEW</code> 每次都会新建一个事务，并且同时将上下文中的事务挂起，执行当前新建事务完成以后，上下文事务恢复再执行。</li><li><code>NOT_SUPPORTED</code> 如果当前上下文中存在事务，则挂起当前事务，然后新的方法在没有事务的环境中执行。</li><li><code>NEVER</code> 如果当前上下文中存在事务，则抛出异常，否则在无事务环境上执行代码。</li><li><code>NESTED</code> 如果当前上下文中存在事务，则嵌套事务执行，如果不存在事务，则新建事务。</li></ul><p>如果我们在手动设置propagation参数的时候，把传播特性设置错了，比如：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">UserService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">propagation</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> Propagation</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">NEVER</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">UserModel</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">userModel</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#82AAFF;">saveData</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#82AAFF;">updateData</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>我们可以看到add方法的事务传播特性定义成了Propagation.NEVER，这种类型的传播特性不支持事务，如果有事务则会抛异常。</p><p>目前只有这三种传播特性才会创建新事务：REQUIRED，REQUIRES_NEW，NESTED。</p><h3 id="_2-自己吞了异常" tabindex="-1">2.自己吞了异常 <a class="header-anchor" href="#_2-自己吞了异常" aria-label="Permalink to &quot;2.自己吞了异常&quot;">​</a></h3><p>事务不会回滚，最常见的问题是：开发者在代码中手动try...catch了异常。比如：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Slf4j</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">UserService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">UserModel</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">userModel</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">try</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#82AAFF;">saveData</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#82AAFF;">updateData</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">catch</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">e</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">error</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">e</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getMessage</span><span style="color:#89DDFF;">(),</span><span style="color:#A6ACCD;"> e</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>这种情况下spring事务当然不会回滚，因为开发者自己捕获了异常，又没有手动抛出，换句话说就是把异常吞掉了。</p><p>如果想要spring事务能够正常回滚，必须抛出它能够处理的异常。如果没有抛异常，则spring认为程序是正常的。</p><h3 id="_3-手动抛了别的异常" tabindex="-1">3.手动抛了别的异常 <a class="header-anchor" href="#_3-手动抛了别的异常" aria-label="Permalink to &quot;3.手动抛了别的异常&quot;">​</a></h3><p>即使开发者没有手动捕获异常，但如果抛的异常不正确，spring事务也不会回滚。</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Slf4j</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">UserService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">UserModel</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">userModel</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">try</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">             </span><span style="color:#82AAFF;">saveData</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">             </span><span style="color:#82AAFF;">updateData</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">catch</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">e</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">error</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">e</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getMessage</span><span style="color:#89DDFF;">(),</span><span style="color:#A6ACCD;"> e</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">throw</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Exception</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">e</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>上面的这种情况，开发人员自己捕获了异常，又手动抛出了异常：Exception，事务同样不会回滚。</p><p>因为spring事务，默认情况下只会回滚<code>RuntimeException</code>（运行时异常）和<code>Error</code>（错误），对于普通的Exception（非运行时异常），它不会回滚。</p><h3 id="_4-自定义了回滚异常" tabindex="-1">4.自定义了回滚异常 <a class="header-anchor" href="#_4-自定义了回滚异常" aria-label="Permalink to &quot;4.自定义了回滚异常&quot;">​</a></h3><p>在使用@Transactional注解声明事务时，有时我们想自定义回滚的异常，spring也是支持的。可以通过设置<code>rollbackFor</code>参数，来完成这个功能。</p><p>但如果这个参数的值设置错了，就会引出一些莫名其妙的问题，例如：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Slf4j</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">UserService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">rollbackFor</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> BusinessException</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">UserModel</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">userModel</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#82AAFF;">saveData</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#82AAFF;">updateData</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>如果在执行上面这段代码，保存和更新数据时，程序报错了，抛了SqlException、DuplicateKeyException等异常。而BusinessException是我们自定义的异常，报错的异常不属于BusinessException，所以事务也不会回滚。</p><p>即使rollbackFor有默认值，但阿里巴巴开发者规范中，还是要求开发者重新指定该参数。</p><p>这是为什么呢？</p><p>因为如果使用默认值，一旦程序抛出了Exception，事务不会回滚，这会出现很大的bug。所以，建议一般情况下，将该参数设置成：Exception或Throwable。</p><h3 id="_5-嵌套事务回滚多了" tabindex="-1">5.嵌套事务回滚多了 <a class="header-anchor" href="#_5-嵌套事务回滚多了" aria-label="Permalink to &quot;5.嵌套事务回滚多了&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">UserService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">UserMapper</span><span style="color:#A6ACCD;"> userMapper</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">RoleService</span><span style="color:#A6ACCD;"> roleService</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">UserModel</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">userModel</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        userMapper</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">insertUser</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        roleService</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">doOtherThing</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">RoleService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">propagation</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> Propagation</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">NESTED</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">doOtherThing</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">保存role表数据</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>这种情况使用了嵌套的内部事务，原本是希望调用roleService.doOtherThing方法时，如果出现了异常，只回滚doOtherThing方法里的内容，不回滚 userMapper.insertUser里的内容，即回滚保存点。。但事实是，insertUser也回滚了。</p><p>why?</p><p>因为doOtherThing方法出现了异常，没有手动捕获，会继续往上抛，到外层add方法的代理方法中捕获了异常。所以，这种情况是直接回滚了整个事务，不只回滚单个保存点。</p><p>怎么样才能只回滚保存点呢？</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Slf4j</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">UserService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">UserMapper</span><span style="color:#A6ACCD;"> userMapper</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">RoleService</span><span style="color:#A6ACCD;"> roleService</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">UserModel</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">userModel</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">        userMapper</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">insertUser</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">try</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            roleService</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">doOtherThing</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">catch</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">e</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">error</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">e</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getMessage</span><span style="color:#89DDFF;">(),</span><span style="color:#A6ACCD;"> e</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>可以将内部嵌套事务放在try/catch中，并且不继续往上抛异常。这样就能保证，如果内部嵌套事务中出现异常，只回滚内部事务，而不影响外部事务。</p><h2 id="三-其他" tabindex="-1">三 其他 <a class="header-anchor" href="#三-其他" aria-label="Permalink to &quot;三 其他&quot;">​</a></h2><h3 id="_1-大事务问题" tabindex="-1">1 大事务问题 <a class="header-anchor" href="#_1-大事务问题" aria-label="Permalink to &quot;1 大事务问题&quot;">​</a></h3><p>在使用spring事务时，有个让人非常头疼的问题，就是大事务问题。</p><p>通常情况下，我们会在方法上<code>@Transactional</code>注解，填加事务功能，比如：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">UserService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">RoleService</span><span style="color:#A6ACCD;"> roleService</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">UserModel</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">userModel</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#82AAFF;">query1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#82AAFF;">query2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#82AAFF;">query3</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">       roleService</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#82AAFF;">update</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">RoleService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">RoleService</span><span style="color:#A6ACCD;"> roleService</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">UserModel</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">userModel</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#82AAFF;">query4</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#82AAFF;">query5</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#82AAFF;">query6</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#82AAFF;">saveData</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>但<code>@Transactional</code>注解，如果被加到方法上，有个缺点就是整个方法都包含在事务当中了。</p><p>上面的这个例子中，在UserService类中，其实只有这两行才需要事务：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">roleService</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#82AAFF;">update</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span></code></pre></div><p>在RoleService类中，只有这一行需要事务：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#82AAFF;">saveData</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">userModel</span><span style="color:#89DDFF;">);</span></span></code></pre></div><p>现在的这种写法，会导致所有的query方法也被包含在同一个事务当中。</p><p>如果query方法非常多，调用层级很深，而且有部分查询方法比较耗时的话，会造成整个事务非常耗时，而从造成大事务问题。</p><p>关于大事务问题的危害，可以阅读一下我的另一篇文章《<a href="https://mp.weixin.qq.com/s?__biz=MzUxODkzNTQ3Nw==&amp;mid=2247485262&amp;idx=1&amp;sn=abe19452e4c13876270f329cc6929be7&amp;chksm=f9800194cef78882e5ad4d8eb00b7e3f745a4159aee6afb1858cc16cae599f8889afa330e17b&amp;token=305097496&amp;lang=zh_CN&amp;scene=21#wechat_redirect" target="_blank" rel="noreferrer">让人头痛的大事务问题到底要如何解决？</a>》，上面有详细的讲解。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205301628745.png" alt="image-20220530162831667" style="zoom:80%;"><h3 id="_2-编程式事务" tabindex="-1">2.编程式事务 <a class="header-anchor" href="#_2-编程式事务" aria-label="Permalink to &quot;2.编程式事务&quot;">​</a></h3><p><code>奇怪，并不能成功运行</code></p><p>上面聊的这些内容都是基于<code>@Transactional</code>注解的，主要说的是它的事务问题，我们把这种事务叫做：<code>声明式事务</code>。</p><p>其实，spring还提供了另外一种创建事务的方式，即通过手动编写代码实现的事务，我们把这种事务叫做：<code>编程式事务</code>。例如：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">TransactionTemplate</span><span style="color:#A6ACCD;"> transactionTemplate</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">...</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">final</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">queryData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">queryData2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">         transactionTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">execute</span><span style="color:#89DDFF;">((</span><span style="color:#A6ACCD;">status</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#82AAFF;">addData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#82AAFF;">updateData2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> Boolean</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">TRUE</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#89DDFF;">})</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">}</span></span></code></pre></div><p>在spring中为了支持编程式事务，专门提供了一个类：TransactionTemplate，在它的execute方法中，就实现了事务的功能。</p><p>相较于<code>@Transactional</code>注解声明式事务，我更建议大家使用，基于<code>TransactionTemplate</code>的编程式事务。主要原因如下：</p><ol><li>避免由于spring aop问题，导致事务失效的问题。</li><li>能够更小粒度的控制事务的范围，更直观。</li></ol><blockquote><p>建议在项目中少使用@Transactional注解开启事务。但并不是说一定不能用它，如果项目中有些业务逻辑比较简单，而且不经常变动，使用@Transactional注解开启事务开启事务也无妨，因为它更简单，开发效率更高，但是千万要小心事务失效的问题。</p></blockquote><h1 id="为什么事务没有生效" tabindex="-1">为什么事务没有生效 <a class="header-anchor" href="#为什么事务没有生效" aria-label="Permalink to &quot;为什么事务没有生效&quot;">​</a></h1><p><strong>用 Spring 的 @Transactional 注解控制事务有哪些不生效的场景？</strong></p><p>不知道小伙伴们有没有这样的经历，在自己开心的编写业务代码时候，突然某一个方法里的事务好像失效了。然后 debug 跟踪代码时发现，自己第一步的 insert 或者 update 的数据在语句执行完毕后，数据库中并没有立即出现更改或保存完的新数据。</p><p>所以一度怀疑spring 的事务失效了。那么这篇文章就来总结一下，大家给大家造成 “spring事务失效”错觉的 几个常见场景，然后对症下药。</p><p>以本人的经历中遇到的问题，大概分有以下几个场景：</p><ul><li>数据库引擎是否支持事务（Mysql 的 MyIsam引擎不支持事务）；</li><li>注解所在的类是否被加载为 Bean（是否被spring 管理）；</li><li>注解所在的方法是否为 public 修饰的；</li><li>是否存在自身调用的问题；</li><li>所用数据源是否加载了事务管理器；</li><li><code>@Transactional</code>的扩展配置<code>propagation</code>是否正确。</li></ul><h2 id="数据库引擎不支持事务" tabindex="-1">数据库引擎不支持事务 <a class="header-anchor" href="#数据库引擎不支持事务" aria-label="Permalink to &quot;数据库引擎不支持事务&quot;">​</a></h2><p>这里以 MySQL 为例，其 MyISAM 引擎是不支持事务操作的，InnoDB 才是支持事务的引擎，一般要支持事务都会使用 InnoDB。</p><p>根据 MySQL 的官方文档：</p><blockquote><p><a href="https://dev.mysql.com/doc/refman/5.5/en/storage-engine-setting.html" target="_blank" rel="noreferrer">https://dev.mysql.com/doc/refman/5.5/en/storage-engine-setting.html</a></p></blockquote><p>从 MySQL 5.5.5 开始的默认存储引擎是：InnoDB，之前默认的都是：MyISAM，所以这点要值得注意，底层引擎不支持事务再怎么搞都是白搭。</p><h2 id="没有被-spring-管理" tabindex="-1">没有被 Spring 管理 <a class="header-anchor" href="#没有被-spring-管理" aria-label="Permalink to &quot;没有被 Spring 管理&quot;">​</a></h2><p>如下面例子所示：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">// @Service  </span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">OrderServiceImpl</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">OrderService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">updateOrder</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Order</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">order</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// update order  </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>如果此时把 <code>@Service</code> 注解注释掉，这个类就不会被加载成一个 Bean，那这个类就不会被 Spring 管理了，事务自然就失效了。</p><h2 id="方法不是-public" tabindex="-1">方法不是 public <a class="header-anchor" href="#方法不是-public" aria-label="Permalink to &quot;方法不是 public&quot;">​</a></h2><p>以下来自 Spring 官方文档：</p><blockquote><p>When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.</p></blockquote><p>大概意思就是 <code>@Transactional</code> 只能用于 public 的方法上，否则事务不会失效，如果要用在非 public 方法上，可以开启 AspectJ 代理模式。</p><h2 id="自身调用问题" tabindex="-1">自身调用问题 <a class="header-anchor" href="#自身调用问题" aria-label="Permalink to &quot;自身调用问题&quot;">​</a></h2><p>来看两个示例：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">//示例1  </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">OrderServiceImpl</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">OrderService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">update</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Order</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">order</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#82AAFF;">updateOrder</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">order</span><span style="color:#89DDFF;">);</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">updateOrder</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Order</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">order</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// update order  </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//示例2  </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">OrderServiceImpl</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">OrderService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">update</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Order</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">order</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#82AAFF;">updateOrder</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">order</span><span style="color:#89DDFF;">);</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">propagation</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> Propagation</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">REQUIRES_NEW</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">updateOrder</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Order</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">order</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// update order  </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><ul><li>示例1 中，update方法上面没有加 <code>@Transactional</code> 注解，调用有 <code>@Transactional</code> 注解的 <code>updateOrder</code> 方法，<code>updateOrder</code> 方法上的事务管用吗？</li><li>示例2 中，update方法上面没有加 <code>@Transactional</code> 注解，调用有 <code>@Transactional</code> 注解的 <code>updateOrder</code> 方法，<code>updateOrder</code> 方法上的事务管用吗？</li></ul><p>这两个例子的答案是：都不管用！</p><p>因为它们发生了自身调用，就调该类自己的方法，而没有经过 Spring 的代理类，默认只有在外部调用事务才会生效，这也是老生常谈的经典问题了。</p><p>这个的解决方案之一就是在的类中注入自己，用注入的对象再调用另外一个方法，这个不太优雅，另外一个可行的方案可以参考《<a href="http://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&amp;mid=2247490867&amp;idx=2&amp;sn=360579d8ac0b9893b00afaee1865ee68&amp;chksm=ebd6221fdca1ab09ae179a8949c8ccb6734d8632503e6f04fc404ae6ef794fd5e49ab26964ef&amp;scene=21#wechat_redirect" target="_blank" rel="noreferrer">Spring 如何在一个事务中开启另一个事务？</a>》这篇文章。</p><h2 id="数据源没有配置事务管理器" tabindex="-1">数据源没有配置事务管理器 <a class="header-anchor" href="#数据源没有配置事务管理器" aria-label="Permalink to &quot;数据源没有配置事务管理器&quot;">​</a></h2><p>如下代码所示，当前数据源若没有配置事务管理器，那也是白搭！</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Bean</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">PlatformTransactionManager</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">transactionManager</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">DataSource</span><span style="color:#A6ACCD;"> dataSource</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">DataSourceTransactionManager</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">dataSource</span><span style="color:#89DDFF;">);</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h2 id="transactional的扩展配置不支持事务" tabindex="-1">@Transactional的扩展配置不支持事务 <a class="header-anchor" href="#transactional的扩展配置不支持事务" aria-label="Permalink to &quot;@Transactional的扩展配置不支持事务&quot;">​</a></h2><p><code>Propagation.NOT_SUPPORTED</code>：表示不以事务运行，当前若存在事务则挂起。这表示不支持以事务的方式运行，所以即使事务生效也是白搭！</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">OrderServiceImpl</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">OrderService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">update</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Order</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">order</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#82AAFF;">updateOrder</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">order</span><span style="color:#89DDFF;">);</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">propagation</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> Propagation</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">NOT_SUPPORTED</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">updateOrder</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Order</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">order</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// update order  </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h2 id="异常被吃了" tabindex="-1">异常被吃了 <a class="header-anchor" href="#异常被吃了" aria-label="Permalink to &quot;异常被吃了&quot;">​</a></h2><p>这个也是出现比较多的场景：把异常吃了，然后又不抛出来，事务也不会回滚！</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">OrderServiceImpl</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">OrderService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">updateOrder</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Order</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">order</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">try</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// update order  </span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">catch</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h2 id="异常类型错误" tabindex="-1">异常类型错误 <a class="header-anchor" href="#异常类型错误" aria-label="Permalink to &quot;异常类型错误&quot;">​</a></h2><p>接上面的例子，再抛出一个异常</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">OrderServiceImpl</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">OrderService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">updateOrder</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Order</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">order</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">try</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// update order  </span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">catch</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">throw</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Exception</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">更新错误</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>这样事务也是不生效的，因为默认回滚的是：<code>RuntimeException</code>，如果你想触发其他异常的回滚，需要在注解上配置一下，如：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">rollbackFor</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> Exception</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">)</span></span></code></pre></div><p>这个配置仅限于 Throwable 异常类及其子类。本文总结了 8 种事务失效的场景，其实发生最多就是自身调用、异常被吃、异常抛出类型不对这 3 个了，像文章开头说的那样，本文不一定总结得全，只是总结常见的事务失效的场景，如果你还知道其他场景也欢迎留言分享。</p><h1 id="解决大事务问题" tabindex="-1">解决大事务问题 <a class="header-anchor" href="#解决大事务问题" aria-label="Permalink to &quot;解决大事务问题&quot;">​</a></h1><p><a href="https://mp.weixin.qq.com/s?__biz=MzkwNjMwMTgzMQ==&amp;mid=2247490259&amp;idx=1&amp;sn=1dd11c5f49103ca303a61fc82ce406e0&amp;chksm=c0ebc23bf79c4b2db58b28ef752560bd91a1932ceb6713c9b19b821db0f29e1c58275d334076&amp;token=2041133408&amp;lang=zh_CN&amp;scene=21#wechat_redirect" target="_blank" rel="noreferrer">让人头痛的大事务问题到底要如何解决？ (qq.com)</a></p><h2 id="大事务引发的问题" tabindex="-1">大事务引发的问题 <a class="header-anchor" href="#大事务引发的问题" aria-label="Permalink to &quot;大事务引发的问题&quot;">​</a></h2><p>在分享解决办法之前，先看看系统中如果出现大事务可能会引发哪些问题</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022/202204211825734.png" alt="image-20220421182555608" style="zoom:67%;"><p>从上图可以看出如果系统中出现大事务时，问题还不小，所以我们在实际项目开发中应该尽量避免大事务的情况。如果我们已有系统中存在大事务问题，该如何解决呢？</p><h2 id="解决办法" tabindex="-1">解决办法 <a class="header-anchor" href="#解决办法" aria-label="Permalink to &quot;解决办法&quot;">​</a></h2><h3 id="少用-transactional注解" tabindex="-1">少用@Transactional注解 <a class="header-anchor" href="#少用-transactional注解" aria-label="Permalink to &quot;少用@Transactional注解&quot;">​</a></h3><p>大家在实际项目开发中，我们在业务方法加上<code>@Transactional</code>注解开启事务功能，这是非常普遍的做法，它被称为<code>声明式事务</code>。</p><p>部分代码如下：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">rollbackFor</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">Exception</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">     doSameThing</span><span style="color:#89DDFF;">...</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>然而，我要说的第一条是：少用<code>@Transactional</code>注解。</p><p>为什么？</p><ol><li>我们知道<code>@Transactional</code>注解是通过<code>spring</code>的<code>aop</code>起作用的，但是如果使用不当，事务功能可能会失效。如果恰巧你经验不足，这种问题不太好排查。至于事务哪些情况下会失效，可以参考我之前写的《<a href="http://mp.weixin.qq.com/s?__biz=MzUxODkzNTQ3Nw==&amp;mid=2247483852&amp;idx=1&amp;sn=e39ff9c4778c1d948347b725e539f8d4&amp;chksm=f9800716cef78e00a8ba890694e030fa910e1fcabe50cac5a9db3c9aaab6b368916a87fa15dd&amp;scene=21#wechat_redirect" target="_blank" rel="noreferrer">spring事务的这10种坑，你稍不注意可能就会踩中！！！</a>》这篇文章。</li><li><code>@Transactional</code>注解一般加在某个业务方法上，会导致整个业务方法都在同一个事务中，粒度太粗，不好控制事务范围，是出现大事务问题的最常见的原因。</li></ol><p>那我们该怎么办呢？</p><p>可以使用<code>编程式事务</code>，在<code>spring</code>项目中使用<code>TransactionTemplate</code>类的对象，手动执行事务。</p><p>部分代码如下：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">TransactionTemplate</span><span style="color:#A6ACCD;"> transactionTemplate</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">...</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">final</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">         transactionTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">execute</span><span style="color:#89DDFF;">((</span><span style="color:#A6ACCD;">status</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            doSameThing</span><span style="color:#89DDFF;">...</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> Boolean</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">TRUE</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#89DDFF;">})</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">}</span></span></code></pre></div><p>从上面的代码中可以看出，使用<code>TransactionTemplate</code>的<code>编程式事务</code>功能自己灵活控制事务的范围，是避免大事务问题的首选办法。</p><p>当然，我说少使用<code>@Transactional</code>注解开启事务，并不是说一定不能用它，如果项目中有些业务逻辑比较简单，而且不经常变动，使用<code>@Transactional</code>注解开启事务开启事务也无妨，因为它更简单，开发效率更高，但是千万要小心事务失效的问题。</p><h3 id="将查询-select-方法放到事务外" tabindex="-1">将查询(select)方法放到事务外 <a class="header-anchor" href="#将查询-select-方法放到事务外" aria-label="Permalink to &quot;将查询(select)方法放到事务外&quot;">​</a></h3><p>如果出现大事务，可以将查询(select)方法放到事务外，也是比较常用的做法，因为一般情况下这类方法是不需要事务的。</p><p>比如出现如下代码：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">rollbackFor</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">Exception</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#82AAFF;">queryData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#82AAFF;">queryData2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#82AAFF;">addData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#82AAFF;">updateData2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>可以将<code>queryData1</code>和<code>queryData2</code>两个查询方法放在事务外执行，将真正需要事务执行的代码才放到事务中，比如：<code>addData1</code>和<code>updateData2</code>方法，这样就能有效的减少事务的粒度。</p><p>如果使用<code>TransactionTemplate</code>的<code>编程式事务</code>这里就非常好修改。</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">TransactionTemplate</span><span style="color:#A6ACCD;"> transactionTemplate</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">...</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">final</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">queryData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">queryData2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">         transactionTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">execute</span><span style="color:#89DDFF;">((</span><span style="color:#A6ACCD;">status</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#82AAFF;">addData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#82AAFF;">updateData2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> Boolean</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">TRUE</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#89DDFF;">})</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">}</span></span></code></pre></div><p>但是如果你实在还是想用<code>@Transactional</code>注解，该怎么拆分呢？</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">queryData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">queryData2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">doSave</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">rollbackFor</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">Exception</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">doSave</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#82AAFF;">addData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#82AAFF;">updateData2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span></code></pre></div><p>这个例子是非常经典的错误，这种直接方法调用的做法事务不会生效，给正在坑中的朋友提个醒。因为<code>@Transactional</code>注解的声明式事务是通过<code>spring aop</code>起作用的，而<code>spring aop</code>需要生成代理对象，直接方法调用使用的还是原始对象，所以事务不会生效。</p><p>有没有办法解决这个问题呢？</p><p>1.新加一个Service方法</p><p>这个方法非常简单，只需要新加一个Service方法，把<code>@Transactional</code>注解加到新Service方法上，把需要事务执行的代码移到新方法中。具体代码如下：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Servcie</span></span>
<span class="line"><span style="color:#A6ACCD;">publicclass ServiceA </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">     prvate </span><span style="color:#C792EA;">ServiceB</span><span style="color:#A6ACCD;"> serviceB</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">           </span><span style="color:#82AAFF;">queryData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">           </span><span style="color:#82AAFF;">queryData2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">           serviceB</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">doSave</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">user</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Servcie</span></span>
<span class="line"><span style="color:#A6ACCD;">   publicclass ServiceB </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">      </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">rollbackFor</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">Exception</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">      </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">doSave</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">addData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">updateData2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">      </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">}</span></span></code></pre></div><p>2.在该Service类中注入自己</p><p>如果不想再新加一个Service类，在该Service类中注入自己也是一种选择。具体代码如下：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Servcie</span></span>
<span class="line"><span style="color:#A6ACCD;">  publicclass ServiceA </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">     prvate </span><span style="color:#C792EA;">ServiceA</span><span style="color:#A6ACCD;"> serviceA</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">           </span><span style="color:#82AAFF;">queryData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">           </span><span style="color:#82AAFF;">queryData2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">           serviceA</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">doSave</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">user</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">     </span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">rollbackFor</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">Exception</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">doSave</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">addData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">updateData2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">      </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">}</span></span></code></pre></div><p>可能有些人可能会有这样的疑问：这种做法会不会出现循环依赖问题？</p><p>其实<code>spring ioc</code>内部的三级缓存保证了它，不会出现循环依赖问题。如果你想进一步了解循环依赖问题，可以看看我之前文章《<a href="http://mp.weixin.qq.com/s?__biz=MzUxODkzNTQ3Nw==&amp;mid=2247483837&amp;idx=1&amp;sn=e5cefc6653a94995f4bc1424ab1087ce&amp;chksm=f9800767cef78e714e974b4fd059915e0bb7962a2a28a9cd657494d9c73fa5a8288a31831973&amp;scene=21#wechat_redirect" target="_blank" rel="noreferrer">spring解决循环依赖为什么要用三级缓存？</a>》。</p><p>3.在该Service类中使用AopContext.currentProxy()获取代理对象</p><p>上面的方法2确实可以解决问题，但是代码看起来并不直观，还可以通过在该Service类中使用AOPProxy获取代理对象，实现相同的功能。具体代码如下：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Servcie</span></span>
<span class="line"><span style="color:#A6ACCD;">  publicclass ServiceA </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">           </span><span style="color:#82AAFF;">queryData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">           </span><span style="color:#82AAFF;">queryData2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">           </span><span style="color:#89DDFF;">((</span><span style="color:#A6ACCD;">ServiceA</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;">AopContext</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">currentProxy</span><span style="color:#89DDFF;">()).</span><span style="color:#82AAFF;">doSave</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">user</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">     </span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">rollbackFor</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">Exception</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">doSave</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">addData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">updateData2</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">      </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">}</span></span></code></pre></div><h3 id="事务中避免远程调用" tabindex="-1">事务中避免远程调用 <a class="header-anchor" href="#事务中避免远程调用" aria-label="Permalink to &quot;事务中避免远程调用&quot;">​</a></h3><p>我们在接口中调用其他系统的接口是不能避免的，由于网络不稳定，这种远程调的响应时间可能比较长，如果远程调用的代码放在某个事物中，这个事物就可能是大事务。当然，远程调用不仅仅是指调用接口，还有包括：发MQ消息，或者连接redis、mongodb保存数据等。</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">rollbackFor</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">Exception</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">callRemoteApi</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">addData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">}</span></span></code></pre></div><p>远程调用的代码可能耗时较长，切记一定要放在事务之外。</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">TransactionTemplate</span><span style="color:#A6ACCD;"> transactionTemplate</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">...</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">final</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">callRemoteApi</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">         transactionTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">execute</span><span style="color:#89DDFF;">((</span><span style="color:#A6ACCD;">status</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#82AAFF;">addData1</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> Boolean</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">TRUE</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#89DDFF;">})</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">}</span></span></code></pre></div><p>有些朋友可能会问，远程调用的代码不放在事务中如何保证数据一致性呢？这就需要建立：<code>重试</code>+<code>补偿机制</code>，达到数据<code>最终一致性</code>了。</p><h3 id="事务中避免一次性处理太多数据" tabindex="-1">事务中避免一次性处理太多数据 <a class="header-anchor" href="#事务中避免一次性处理太多数据" aria-label="Permalink to &quot;事务中避免一次性处理太多数据&quot;">​</a></h3><p>如果一个事务中需要处理的数据太多，也会造成大事务问题。比如为了操作方便，你可能会一次批量更新1000条数据，这样会导致大量数据锁等待，特别在高并发的系统中问题尤为明显。</p><p>解决办法是分页处理，1000条数据，分50页，一次只处理20条数据，这样可以大大减少大事务的出现。</p><h3 id="非事务执行" tabindex="-1">非事务执行 <a class="header-anchor" href="#非事务执行" aria-label="Permalink to &quot;非事务执行&quot;">​</a></h3><p>在使用事务之前，我们都应该思考一下，是不是所有的数据库操作都需要在事务中执行？</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">TransactionTemplate</span><span style="color:#A6ACCD;"> transactionTemplate</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">...</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">final</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">         transactionTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">execute</span><span style="color:#89DDFF;">((</span><span style="color:#A6ACCD;">status</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#82AAFF;">addData</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#82AAFF;">addLog</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#82AAFF;">updateCount</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> Boolean</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">TRUE</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#89DDFF;">})</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">}</span></span></code></pre></div><p>上面的例子中，其实<code>addLog</code>增加操作日志方法 和 <code>updateCount</code>更新统计数量方法，是可以不在事务中执行的，因为操作日志和统计数量这种业务允许少量数据不一致的情况。</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">TransactionTemplate</span><span style="color:#A6ACCD;"> transactionTemplate</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">...</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">final</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">         transactionTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">execute</span><span style="color:#89DDFF;">((</span><span style="color:#A6ACCD;">status</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#82AAFF;">addData</span><span style="color:#89DDFF;">();</span><span style="color:#A6ACCD;">           </span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> Boolean</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">TRUE</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#89DDFF;">})</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">addLog</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">updateCount</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">}</span></span></code></pre></div><p>当然大事务中要鉴别出哪些方法可以非事务执行，其实没那么容易，需要对整个业务梳理一遍，才能找出最合理的答案。</p><h3 id="异步处理" tabindex="-1">异步处理 <a class="header-anchor" href="#异步处理" aria-label="Permalink to &quot;异步处理&quot;">​</a></h3><p>还有一点也非常重要，是不是事务中的所有方法都需要同步执行？我们都知道，方法同步执行需要等待方法返回，如果一个事务中同步执行的方法太多了，势必会造成等待时间过长，出现大事务问题。</p><p>看看下面这个列子：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">TransactionTemplate</span><span style="color:#A6ACCD;"> transactionTemplate</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">...</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">final</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">         transactionTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">execute</span><span style="color:#89DDFF;">((</span><span style="color:#A6ACCD;">status</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#82AAFF;">order</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#82AAFF;">delivery</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> Boolean</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">TRUE</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#89DDFF;">})</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">}</span></span></code></pre></div><p><code>order</code>方法用于下单，<code>delivery</code>方法用于发货，是不是下单后就一定要马上发货呢？</p><p>答案是否定的。</p><p>这里发货功能其实可以走mq异步处理逻辑。</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">TransactionTemplate</span><span style="color:#A6ACCD;"> transactionTemplate</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">...</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">final</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">         transactionTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">execute</span><span style="color:#89DDFF;">((</span><span style="color:#A6ACCD;">status</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#82AAFF;">order</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> Boolean</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">TRUE</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#89DDFF;">})</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#82AAFF;">sendMq</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">}</span></span></code></pre></div><h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">​</a></h2><p>本人从网友的一个问题出发，结合自己实际的工作经验分享了处理大事务的6种办法：</p><ol><li>少用@Transactional注解</li><li>将查询(select)方法放到事务外</li><li>事务中避免远程调用</li><li>事务中避免一次性处理太多数据</li><li>非事务执行</li><li>异步处理</li></ol><h1 id="transactional-注解" tabindex="-1">@Transactional 注解 <a class="header-anchor" href="#transactional-注解" aria-label="Permalink to &quot;@Transactional 注解&quot;">​</a></h1><p>在Spring中进行事务管理非常简单，只需要在方法上加上注解<code>@Transactional</code>，Spring就可以自动帮我们进行事务的开启、提交、回滚操作。甚至很多人心里已经将Spring事务与<code>@Transactional</code>划上了等号，只要有数据库相关操作就直接给方法加上<code>@Transactional</code>注解。</p><p>不瞒你说，我之前也一直是这样，直到使用<code>@Transactional</code>导致了一次生产事故，而那次生产事故还导致我当月绩效被打了D...</p><h2 id="transactional导致的生产事故" tabindex="-1">@Transactional导致的生产事故 <a class="header-anchor" href="#transactional导致的生产事故" aria-label="Permalink to &quot;@Transactional导致的生产事故&quot;">​</a></h2><p>19年在公司做了一个内部报销的项目，有这样一个业务逻辑：</p><p>1、员工加班打车可以通过滴滴出行企业版直接打车，第二天打车费用可以直接同步到我们的报销平台</p><p>2、员工可以在报销平台勾选自己打车费用并创建一张报销单进行报销，创建报销单的同时会创建一条审批流（统一流程平台）让领导审批</p><p>当时创建报销单的代码是这么写的：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">/**</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"> * 保存报销单并创建工作流</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"> */</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Transactional</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">rollbackFor</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> Exception</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">RequestBillDTO</span><span style="color:#A6ACCD;"> requestBillDTO</span><span style="color:#89DDFF;">){</span></span>
<span class="line"><span style="color:#89DDFF;">     </span><span style="color:#676E95;font-style:italic;">//调用流程HTTP接口创建工作流</span></span>
<span class="line"><span style="color:#A6ACCD;">    workflowUtil</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">createFlow</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">BILL</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">requestBillDTO</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//转换DTO对象</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">RequestBill</span><span style="color:#A6ACCD;"> requestBill </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> JkMappingUtils</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">convert</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">requestBillDTO</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> RequestBill</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    requestBillDao</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">requestBill</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//保存明细表</span></span>
<span class="line"><span style="color:#A6ACCD;">    requestDetailDao</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">requestBill</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getDetail</span><span style="color:#89DDFF;">())</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>代码非常简单也很 “<strong>优雅</strong>”，先通过http接口调用工作流引擎创建审批流，然后保存报销单，而为了<strong>保证</strong>操作的事务，在整个方法上加上了<code>@Transactional</code>注解（<strong>仔细想想，这样真的能保证事务吗？</strong>）。</p><p>报销项目属于公司内部项目，本身是没什么高并发的，系统也一直稳定运行着。</p><p>在年末的一天下午（前几天刚好下了大雪，打车的人特别多），公司发通知邮件说年度报销窗口即将关闭，需要尽快将未报销的费用报销掉，而刚好那天工作流引擎在进行安全加固。</p><p>收到邮件后报销的人开始逐渐增多，在接近下班的时候到达顶峰，此时报销系统开始出现了故障：<strong>数据库监控平台一直收到告警短信，数据库连接不足，出现大量死锁；日志显示调用流程引擎接口出现大量超时；同时一直提示<code>CannotGetJdbcConnectionException</code>，数据库连接池连接占满。</strong></p><p>在发生故障后，我们尝试过杀掉死锁进程，也进行过暴力重启，只是不到10分钟故障再次出现，收到大量电话投诉。 最后没办法只能向全员发送停机维护邮件并发送故障报告，而后，绩效被打了个D，惨...。</p><h2 id="事故原因分析" tabindex="-1">事故原因分析 <a class="header-anchor" href="#事故原因分析" aria-label="Permalink to &quot;事故原因分析&quot;">​</a></h2><p>通过对日志的分析我们很容易就可以定位到故障原因就是保存报销单的save()方法，而罪魁祸首就是那个<code>@Transactional</code>注解。</p><p>我们知道<code>@Transactional</code> 注解，是使用 AOP 实现的，本质就是在目标方法执行前后进行拦截。在目标方法执行前加入或创建一个事务，在执行方法执行后，根据实际情况选择提交或是回滚事务。</p><p>当 Spring 遇到该注解时，会自动从数据库连接池中获取 connection，并开启事务然后绑定到 ThreadLocal 上，对于@Transactional注解包裹的整个方法都是<strong>使用同一个connection连接</strong>。如果我们出现了耗时的操作，比如第三方接口调用，业务逻辑复杂，大批量数据处理等就会导致我们我们占用这个connection的时间会很长，数据库连接一直被占用不释放。一旦类似操作过多，就会导致数据库连接池耗尽。</p><p>在一个事务中执行RPC操作导致数据库连接池撑爆属于是典型的<strong>长事务问题</strong>，类似的操作还有在事务中进行大量数据查询，业务规则处理等...</p><p><strong>何为长事务？</strong></p><p>顾名思义就是运行时间比较长，长时间未提交的事务，也可以称之为<strong>大事务</strong>。</p><p><strong>长事务会引发哪些问题？</strong></p><p>长事务引发的常见危害有：</p><ol><li>数据库连接池被占满，应用无法获取连接资源；</li><li>容易引发数据库死锁；</li><li>数据库回滚时间长；</li><li>在主从架构中会导致主从延时变大。</li></ol><h2 id="如何避免长事务" tabindex="-1">如何避免长事务？ <a class="header-anchor" href="#如何避免长事务" aria-label="Permalink to &quot;如何避免长事务？&quot;">​</a></h2><p>既然知道了长事务的危害，那如何在开发中避免出现长事务问题呢？</p><p>很明显，解决长事务的宗旨就是 <strong>对事务方法进行拆分，尽量让事务变小，变快，减小事务的颗粒度。</strong></p><p>既然提到了事务的颗粒度，我们就先回顾一下Spring进行事务管理的方式。</p><p><strong>声明式事务</strong></p><p>首先我们要知道，通过在方法上使用<code>@Transactional</code>注解进行事务管理的操作叫<strong>声明式事务</strong> 。</p><p>使用声明式事务的<strong>优点</strong> 很明显，就是使用很简单，可以自动帮我们进行事务的开启、提交以及回滚等操作。使用这种方式，程序员只需要关注业务逻辑就可以了。</p><p>声明式事务有一个最大的<strong>缺点</strong>，就是事务的颗粒度是整个方法，无法进行精细化控制。</p><p>与声明式事务对应的就是<strong>编程式事务</strong>。</p><p>基于底层的API，开发者在代码中手动的管理事务的开启、提交、回滚等操作。在spring项目中可以使用<code>TransactionTemplate</code>类的对象，手动控制事务。</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@Autowired </span></span>
<span class="line"><span style="color:#A6ACCD;">private TransactionTemplate transactionTemplate; </span></span>
<span class="line"><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">... </span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">public void save(RequestBill requestBill) { </span></span>
<span class="line"><span style="color:#A6ACCD;">    transactionTemplate.execute(transactionStatus -&gt; {</span></span>
<span class="line"><span style="color:#A6ACCD;">        requestBillDao.save(requestBill);</span></span>
<span class="line"><span style="color:#A6ACCD;">        //保存明细表</span></span>
<span class="line"><span style="color:#A6ACCD;">        requestDetailDao.save(requestBill.getDetail());</span></span>
<span class="line"><span style="color:#A6ACCD;">        return Boolean.TRUE; </span></span>
<span class="line"><span style="color:#A6ACCD;">    });</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>使用编程式事务最大的好处就是可以精细化控制事务范围。</p><p>所以避免长事务最简单的方法就是<strong>不要使用声明式事务<code>@Transactional</code>，而是使用编程式事务手动控制事务范围。</strong></p><p>有的同学会说，<code>@Transactional</code>使用这么简单，有没有办法既可以使用<code>@Transactional</code>，又能避免产生长事务？</p><p>那就需要对方法进行拆分，将不需要事务管理的逻辑与事务操作分开：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@Service</span></span>
<span class="line"><span style="color:#A6ACCD;">public class OrderService{</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    public void createOrder(OrderCreateDTO createDTO){</span></span>
<span class="line"><span style="color:#A6ACCD;">        query();</span></span>
<span class="line"><span style="color:#A6ACCD;">        validate();</span></span>
<span class="line"><span style="color:#A6ACCD;">        saveData(createDTO);</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">  //事务操作</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Transactional(rollbackFor = Throwable.class)</span></span>
<span class="line"><span style="color:#A6ACCD;">    public void saveData(OrderCreateDTO createDTO){</span></span>
<span class="line"><span style="color:#A6ACCD;">        orderDao.insert(createDTO);</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p><code>query()</code>与<code>validate()</code>不需要事务，我们将其与事务方法<code>saveData()</code>拆开。</p><p>当然，这种拆分会命中使用<code>@Transactional</code>注解时事务不生效的经典场景，很多新手非常容易犯这个错误。<code>@Transactional</code>注解的声明式事务是通过spring aop起作用的，而spring aop需要生成代理对象，直接在同一个类中方法调用使用的还是原始对象，事务不生效。其他几个常见的事务不生效的场景为：</p><blockquote><ul><li>@Transactional 应用在非 public 修饰的方法上</li><li>@Transactional 注解属性 propagation 设置错误</li><li>@Transactional 注解属性 rollbackFor 设置错误</li><li>同一个类中方法调用，导致@Transactional失效</li><li>异常被catch捕获导致@Transactional失效</li></ul></blockquote><p>正确的拆分方法应该使用下面两种：</p><ol><li>可以将方法放入另一个类，如新增 <code>manager层</code>，通过spring注入，这样符合了在对象之间调用的条件。</li></ol><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@Service</span></span>
<span class="line"><span style="color:#A6ACCD;">public class OrderService{</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">   private OrderManager orderManager;</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    public void createOrder(OrderCreateDTO createDTO){</span></span>
<span class="line"><span style="color:#A6ACCD;">        query();</span></span>
<span class="line"><span style="color:#A6ACCD;">        validate();</span></span>
<span class="line"><span style="color:#A6ACCD;">        orderManager.saveData(createDTO);</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">@Service</span></span>
<span class="line"><span style="color:#A6ACCD;">public class OrderManager{</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">    @Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">   private OrderDao orderDao;</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">  @Transactional(rollbackFor = Throwable.class)</span></span>
<span class="line"><span style="color:#A6ACCD;">    public void saveData(OrderCreateDTO createDTO){</span></span>
<span class="line"><span style="color:#A6ACCD;">        orderDao.saveData(createDTO);</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><ol><li>启动类添加<code>@EnableAspectJAutoProxy(exposeProxy = true)</code>，方法内使用<code>AopContext.currentProxy()</code>获得代理类，使用事务。</li></ol><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">SpringBootApplication.java</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">@EnableAspectJAutoProxy(exposeProxy = true)</span></span>
<span class="line"><span style="color:#A6ACCD;">@SpringBootApplication</span></span>
<span class="line"><span style="color:#A6ACCD;">public class SpringBootApplication {}</span></span>
<span class="line"><span style="color:#A6ACCD;">OrderService.java</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">public void createOrder(OrderCreateDTO createDTO){</span></span>
<span class="line"><span style="color:#A6ACCD;">    OrderService orderService = (OrderService)AopContext.currentProxy();</span></span>
<span class="line"><span style="color:#A6ACCD;">    orderService.saveData(createDTO);</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><h2 id="小结" tabindex="-1">小结 <a class="header-anchor" href="#小结" aria-label="Permalink to &quot;小结&quot;">​</a></h2><p>使用<code>@Transactional</code>注解在开发时确实很方便，但是稍微不注意就可能出现长事务问题。所以对于复杂业务逻辑，我这里更建议你使用编程式事务来管理事务，当然，如果你非要使用<code>@Transactional</code>，可以根据上文提到的两种方案进行方法拆分。</p><h1 id="autowired报错的4种解决方案" tabindex="-1">@Autowired报错的4种解决方案 <a class="header-anchor" href="#autowired报错的4种解决方案" aria-label="Permalink to &quot;@Autowired报错的4种解决方案&quot;">​</a></h1><p>注入mapper接口报错</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061746268.png" alt="image-20220706174612205" style="zoom:50%;"><p>上图的报错信息相信大部分程序员都遇到过，<strong>奇怪的是虽然代码报错，但丝毫不影响程序的正常执行</strong>，也就是虽然编译器 IDEA 报错，但程序却能正常的执行，那这其中的原因又是为何？</p><h2 id="报错原因分析" tabindex="-1">报错原因分析 <a class="header-anchor" href="#报错原因分析" aria-label="Permalink to &quot;报错原因分析&quot;">​</a></h2><p>报错的原因首先是因为 IDEA 强大的报警机制，@Autowired 为 Spring 的注解，含义是将某类动态的注入到当前类中，如下图所示：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061746618.png" alt="image-20220706174640558" style="zoom:50%;"><p>@Autowired 默认是根据 type 进行注入，并且注入时要求（注入）对象不能为 NULL，默认值如下图所示：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061746060.png" alt="image-20220706174650999" style="zoom:50%;"><p>而 <strong>IDEA 报错的原因是：@Autowired 为 Spring 的注解，而注入的 Mapper 对象使用的又是 @Mapper 的注解，然而 @Mapper 又为 MyBaits 的注解，IDEA 能很好的兼容并识别 Spring 的注解，但不能很好的识别 MyBatis 的注解，因此在使用 @Autowired 注解时，IDEA 并不能检测到 @Mapper 注解的对象不为 NULL，因此就会报错。</strong><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061747911.png" alt="image-20220706174710840" style="zoom:50%;"></p><p>这就是为什么使用 Spring 的注解 @Repository/@Component... 不报错，而使用 @Mapper 注解却会报错的根本原因，如下图所示：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061747556.png" alt="image-20220706174747492" style="zoom:50%;"><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061748311.png" alt="image-20220706174807224" style="zoom:50%;"><h2 id="解决方案1-关闭报警机制" tabindex="-1">解决方案1：关闭报警机制 <a class="header-anchor" href="#解决方案1-关闭报警机制" aria-label="Permalink to &quot;解决方案1：关闭报警机制&quot;">​</a></h2><p>关闭 IDEA 注入报警机制，可以避免报错，实现步骤如下。</p><p>1.打开 IDEA，找到参数设置选项 “Preferences...” ，如下图所示：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061748452.png" alt="image-20220706174822240" style="zoom:50%;"><p>2.依次选择 “Editor” -&gt; “Inspections” -&gt; “Spring” -&gt; “Spring Core” -&gt; “Code” -&gt; “Autowiring for bean class” 将 “Error” 级别修改成 “Waring” 级别，如下图所示：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061748877.png" alt="image-20220706174833788" style="zoom:50%;"><p>设置完成之后点击确认，查看之前报错的 Mapper 类，此时展示效果如下：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061748519.png" alt="image-20220706174843470" style="zoom:50%;"><p>报错信息消失了。</p><h2 id="解决方案2-添加spring注解" tabindex="-1">解决方案2：添加Spring注解 <a class="header-anchor" href="#解决方案2-添加spring注解" aria-label="Permalink to &quot;解决方案2：添加Spring注解&quot;">​</a></h2><p>在 Mapper 的类上添加 Spring 的注解，也可以解决 IDEA 报错的问题，如 @Repository 或 @Component 这类注解，如下图所示：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061745921.png" alt="image-20220706174552854" style="zoom:50%;"><p>或使用 @Repository 注解，如下图所示：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061745405.png" alt="image-20220706174540340" style="zoom:50%;"><p>查看之前的报错信息：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061745736.png" alt="image-20220706174529687" style="zoom:50%;"><p>报错消失了。</p><h2 id="解决方案3-允许注入对象为null" tabindex="-1">解决方案3：允许注入对象为NULL <a class="header-anchor" href="#解决方案3-允许注入对象为null" aria-label="Permalink to &quot;解决方案3：允许注入对象为NULL&quot;">​</a></h2><p>设置允许注入的 Mapper 对象为 NULL，也可以避免 IDEA 报错，只需要设置 @Autowired(required=false) 即可，如下图所示：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061745112.png" alt="image-20220706174515049" style="zoom:50%;"><p>（其中 userMapper2 对象就不报错了）</p><ul><li>@Autowired(required=true)：表示当使用 @Autowired 注解的时候，该 bean 必须存在，否则注入失败，默认值。</li><li>@Autowired(required=false)：表示忽略当前要注入的 bean，如果有直接注入，没有则跳过，不会报错。</li></ul><p>@Autowired 默认值的实现源码：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061745131.png" alt="image-20220706174503064" style="zoom:50%;"><h2 id="解决方案4-使用-resource注解" tabindex="-1">解决方案4：使用@Resource注解 <a class="header-anchor" href="#解决方案4-使用-resource注解" aria-label="Permalink to &quot;解决方案4：使用@Resource注解&quot;">​</a></h2><p>使用 @Resource 注解替换 @Autowired 注解也可以避免报错，它们的对比效果如下：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061744418.png" alt="image-20220706174449355" style="zoom:50%;"><p>@Resource 注解和 @Autowired 注解以当前的场景来说，它们的主要区别是 @Resource 是 Java 自身提供的注解，而 @Autowired 是 Spring 提供的注解，@Autowired 默认值为 required=true，所以必须要一个非 NULL 的对象，当 IDEA 检测不到对象为 NULL 时就会报错，而 @Resource 并没有这项要求。</p><h2 id="总结-1" tabindex="-1">总结 <a class="header-anchor" href="#总结-1" aria-label="Permalink to &quot;总结&quot;">​</a></h2><blockquote><p>使用 @Autowired 注解导入 Mapper 对象报错的原因，是因为 @Autowired 默认情况下，需要注入一个非 NULL 的对象，而被 @Mapper 修饰的类为 MyBatis 的注解，IDEA 并不能很好的识别其为非 NULL 对象，因此就会报错。当然，它的解决方案也有很多，推荐使用 @Resource 替代 @Autowired 注解的方式来解决此问题。</p></blockquote><h1 id="service详解" tabindex="-1">@Service详解 <a class="header-anchor" href="#service详解" aria-label="Permalink to &quot;@Service详解&quot;">​</a></h1><blockquote><p>Spring中如Service有多个实现类，它怎么知道该注入哪个ServiceImpl类？</p></blockquote><h2 id="三个方法" tabindex="-1">三个方法 <a class="header-anchor" href="#三个方法" aria-label="Permalink to &quot;三个方法&quot;">​</a></h2><p><strong>方法一：</strong> Controller中注入service的时候使用@Autowired自动注入，<code>@Qualifier(&quot;beanId&quot;)</code>来指定注入哪一个。</p><p><strong>方法二：</strong> Controller中注入service的时候使用<code>@Resource(type = 类名.class)</code>来指定注入哪一个。</p><p><strong>方法三：</strong></p><ol><li>每个service的impl都可以指定名称（使用<code>@Service（“名称”）</code>）</li><li>Controller中注入service的时候使用名称来指定注入哪一个（使用<code>@Resource(name=&quot;名称&quot;)</code>）。</li></ol><p><strong>@Service注解，其实做了两件事情：</strong></p><p>1、声明TeacherServiceImpl.java是一个bean。因为TeacherServiceImpl .java是一个bean，其他的类才可以使用@Autowired将TeacherServiceImpl 作为一个成员变量自动注入。</p><p>2、TeacherServiceImpl.java在bean中的id是&quot;teacherServiceImpl &quot;，即类名且首字母小写。</p><blockquote><p>注意：不能有同名的，不然要报错。</p></blockquote><p><strong>@Autowired注解的意思就是:</strong></p><p>当Spring发现<code>@Autowired</code>注解时，将自动在代码上下文中找到和其匹配（默认是类型匹配）的Bean，并自动注入到相应的地方去。</p><p><code>@Resource</code>的作用相当于<code>@Autowired</code>。</p><p><strong>@Autowired和@Resource两个注解的区别：</strong></p><p>1.<code>@Autowired</code>是Spring的注解，<code>@Resource</code>是J2EE的注解，这个看一下导入注解的时候这两个注解的包名就一清二楚了。</p><p>2.<code>@Autowired</code>默认按照byType方式进行bean匹配，<code>@Resource</code>默认按照byName方式进行bean匹配。</p><p>3.@Autowired默认情况下必须要求依赖对象必须存在，如果要允许null值，可以设置它的required属性为false，如：<code>@Autowired(required=false)</code>。</p><h2 id="代码实现" tabindex="-1">代码实现 <a class="header-anchor" href="#代码实现" aria-label="Permalink to &quot;代码实现&quot;">​</a></h2><h4 id="方法一代码如下" tabindex="-1">方法一代码如下 <a class="header-anchor" href="#方法一代码如下" aria-label="Permalink to &quot;方法一代码如下&quot;">​</a></h4><p>接口</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">interface</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">HumanService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">name</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>接口实现类</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">TeacherServiceImpl</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">HumanService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">name</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">teacher</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">teacher</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">DoctorServiceImpl</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">HumanService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">name</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">doctor</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">doctor</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>控制器</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">RestController</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">HumanController</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//    @Resource(type = DoctorServiceImpl.class) //方法二</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Qualifier</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">teacherServiceImpl</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">HumanService</span><span style="color:#A6ACCD;"> humanService</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">RequestMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">/name</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">name</span><span style="color:#89DDFF;">(){</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> humanService</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">name</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h4 id="方法三代码如下" tabindex="-1">方法三代码如下 <a class="header-anchor" href="#方法三代码如下" aria-label="Permalink to &quot;方法三代码如下&quot;">​</a></h4><p>接口</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">interface</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">HumanService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">name</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>接口实现类</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">teacherService</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">TeacherServiceImpl</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">HumanService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">name</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">teacher</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">teacher</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">doctorService</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">DoctorServiceImpl</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">HumanService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">name</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">doctor</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">doctor</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>控制器</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">RestController</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">HumanController</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Resource</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">name</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">doctorService</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">HumanService</span><span style="color:#A6ACCD;"> humanService</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">RequestMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">/name</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">name</span><span style="color:#89DDFF;">(){</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> humanService</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">name</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h1 id="_11个小技巧-玩转spring" tabindex="-1">11个小技巧，玩转Spring <a class="header-anchor" href="#_11个小技巧-玩转spring" aria-label="Permalink to &quot;11个小技巧，玩转Spring&quot;">​</a></h1><h2 id="前言" tabindex="-1">前言 <a class="header-anchor" href="#前言" aria-label="Permalink to &quot;前言&quot;">​</a></h2><p>最近有些读者私信我说希望后面多分享spring方面的文章，这样能够在实际工作中派上用场。正好我对spring源码有过一定的研究，并结合我这几年实际的工作经验，把spring中我认为不错的知识点总结一下，希望对您有所帮助。</p><h2 id="一-如何获取spring容器对象" tabindex="-1">一 如何获取spring容器对象 <a class="header-anchor" href="#一-如何获取spring容器对象" aria-label="Permalink to &quot;一 如何获取spring容器对象&quot;">​</a></h2><h3 id="_1-实现beanfactoryaware接口" tabindex="-1">1.实现BeanFactoryAware接口 <a class="header-anchor" href="#_1-实现beanfactoryaware接口" aria-label="Permalink to &quot;1.实现BeanFactoryAware接口&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">PersonService</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">BeanFactoryAware</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">BeanFactory</span><span style="color:#A6ACCD;"> beanFactory</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">setBeanFactory</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">BeanFactory</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">beanFactory</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">BeansException</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">this.</span><span style="color:#A6ACCD;">beanFactory </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> beanFactory</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">Person</span><span style="color:#A6ACCD;"> person </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">Person</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> beanFactory</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getBean</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">person</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>实现接口，然后重写方法，就能从该方法中获取到spring容器对象。<code>BeanFactoryAware``setBeanFactory</code></p><h3 id="_2-实现applicationcontextaware接口" tabindex="-1">2.实现ApplicationContextAware接口 <a class="header-anchor" href="#_2-实现applicationcontextaware接口" aria-label="Permalink to &quot;2.实现ApplicationContextAware接口&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">PersonService2</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">ApplicationContextAware</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">ApplicationContext</span><span style="color:#A6ACCD;"> applicationContext</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">setApplicationContext</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">ApplicationContext</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">applicationContext</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">BeansException</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">this.</span><span style="color:#A6ACCD;">applicationContext </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> applicationContext</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">Person</span><span style="color:#A6ACCD;"> person </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">Person</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> applicationContext</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getBean</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">person</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>实现接口，然后重写方法，也能从该方法中获取到spring容器对象。<code>ApplicationContextAware``setApplicationContext</code></p><h3 id="_3-实现applicationlistener接口" tabindex="-1">3.实现ApplicationListener接口 <a class="header-anchor" href="#_3-实现applicationlistener接口" aria-label="Permalink to &quot;3.实现ApplicationListener接口&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">PersonService3</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">ApplicationListener</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">ContextRefreshedEvent</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">ApplicationContext</span><span style="color:#A6ACCD;"> applicationContext</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">onApplicationEvent</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">ContextRefreshedEvent</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">event</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        applicationContext </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> event</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getApplicationContext</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">Person</span><span style="color:#A6ACCD;"> person </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">Person</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> applicationContext</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getBean</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">person</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>实现接口，需要注意的是该接口接收的泛型是类，然后重写方法，也能从该方法中获取到spring容器对象。<code>ApplicationListener``ContextRefreshedEvent``onApplicationEvent</code></p><p>此外，不得不提一下接口，它其实是一个空接口，里面不包含任何方法。<code>Aware</code></p><p>它表示已感知的意思，通过这类接口可以获取指定对象，比如：</p><ul><li>通过BeanFactoryAware获取BeanFactory</li><li>通过ApplicationContextAware获取ApplicationContext</li><li>通过BeanNameAware获取BeanName等</li></ul><p>Aware接口是很常用的功能，目前包含如下功能：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061812197.png" alt="image-20220706181224112" style="zoom:50%;"><h2 id="二-如何初始化bean" tabindex="-1">二 如何初始化bean <a class="header-anchor" href="#二-如何初始化bean" aria-label="Permalink to &quot;二 如何初始化bean&quot;">​</a></h2><p>spring中支持3种初始化bean的方法：</p><ul><li>xml中指定init-method方法</li><li>使用@PostConstruct注解</li><li>实现InitializingBean接口</li></ul><p>第一种方法太古老了，现在用的人不多，具体用法就不介绍了。</p><h3 id="_1-使用-postconstruct注解" tabindex="-1">1.使用@PostConstruct注解 <a class="header-anchor" href="#_1-使用-postconstruct注解" aria-label="Permalink to &quot;1.使用@PostConstruct注解&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">AService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">PostConstruct</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">init</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">===初始化===</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>在需要初始化的方法上增加注解，这样就有初始化的能力。<code>@PostConstruct</code></p><h3 id="_2-实现initializingbean接口" tabindex="-1">2.实现InitializingBean接口 <a class="header-anchor" href="#_2-实现initializingbean接口" aria-label="Permalink to &quot;2.实现InitializingBean接口&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">BService</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">InitializingBean</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">afterPropertiesSet</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">===初始化===</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>实现接口，重写方法，该方法中可以完成初始化功能。<code>InitializingBean``afterPropertiesSet</code></p><p>这里顺便抛出一个有趣的问题：、 和 的执行顺序是什么样的？<code>init-method``PostConstruct``InitializingBean</code></p><p>决定他们调用顺序的关键代码在类的方法中。<code>AbstractAutowireCapableBeanFactory``initializeBean</code></p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061813599.png" alt="image-20220706181311516" style="zoom:67%;"><p>这段代码中会先调用的方法，而是通过实现的，它就是一个，所以先执行。<code>BeanPostProcessor``postProcessBeforeInitialization``PostConstruct``InitDestroyAnnotationBeanPostProcessor``BeanPostProcessor``PostConstruct</code></p><p>而方法中的代码：<code>invokeInitMethods</code></p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061813936.png" alt="image-20220706181325830" style="zoom:50%;"><p>决定了先调用，再调用。<code>InitializingBean``init-method</code></p><p>所以得出结论，他们的调用顺序是：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061813740.png" alt="image-20220706181342606" style="zoom:33%;"><h2 id="三-自定义自己的scope" tabindex="-1">三 自定义自己的Scope <a class="header-anchor" href="#三-自定义自己的scope" aria-label="Permalink to &quot;三 自定义自己的Scope&quot;">​</a></h2><p>我们都知道默认支持的只有两种：<code>spring``Scope</code></p><ul><li>singleton 单例，每次从spring容器中获取到的bean都是同一个对象。</li><li>prototype 多例，每次从spring容器中获取到的bean都是不同的对象。</li></ul><div class="language-c"><button title="Copy Code" class="copy"></button><span class="lang">c</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">spring web`又对进行了扩展，增加了：`Scope</span></span></code></pre></div><ul><li>RequestScope 同一次请求从spring容器中获取到的bean都是同一个对象。</li><li>SessionScope 同一个会话从spring容器中获取到的bean都是同一个对象。</li></ul><p>即便如此，有些场景还是无法满足我们的要求。</p><p>比如，我们想在同一个线程中从spring容器获取到的bean都是同一个对象，该怎么办？</p><p>这就需要自定义了。<code>Scope</code></p><p>第一步实现接口：<code>Scope</code></p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">ThreadLocalScope</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Scope</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">final</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">ThreadLocal</span><span style="color:#A6ACCD;"> THREAD_LOCAL_SCOPE </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">ThreadLocal</span><span style="color:#89DDFF;">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">get</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">name</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">ObjectFactory</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">?</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">objectFactory</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> value </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> THREAD_LOCAL_SCOPE</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">get</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">value </span><span style="color:#89DDFF;">!=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">null)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> value</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> object </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> objectFactory</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getObject</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        THREAD_LOCAL_SCOPE</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">set</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">object</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> object</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">remove</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">name</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        THREAD_LOCAL_SCOPE</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">remove</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">null;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">registerDestructionCallback</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">name</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Runnable</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">callback</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">resolveContextualObject</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">key</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">null;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">getConversationId</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">null;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>第二步将新定义的注入到spring容器中：<code>Scope</code></p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Component</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">ThreadLocalBeanFactoryPostProcessor</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">BeanFactoryPostProcessor</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">postProcessBeanFactory</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">ConfigurableListableBeanFactory</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">beanFactory</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">BeansException</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        beanFactory</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">registerScope</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">threadLocalScope</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">ThreadLocalScope</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>第三步使用新定义的：<code>Scope</code></p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Scope</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">threadLocalScope</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">CService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h2 id="四-别说factorybean没用" tabindex="-1">四 别说FactoryBean没用 <a class="header-anchor" href="#四-别说factorybean没用" aria-label="Permalink to &quot;四 别说FactoryBean没用&quot;">​</a></h2><p>说起就不得不提，因为面试官老喜欢问它们的区别。<code>FactoryBean``BeanFactory</code></p><ul><li>BeanFactory：spring容器的顶级接口，管理bean的工厂。</li><li>FactoryBean：并非普通的工厂bean，它隐藏了实例化一些复杂Bean的细节，给上层应用带来了便利。</li></ul><p>如果你看过spring源码，会发现它有70多个地方在用FactoryBean接口。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/uL371281oDELdgnZC2EVRmh8HYBrPD7vTVYWc3BZ64373MkUZRhVDe8w0uA6s9k7RSwl5siabkvMVRVa4ycpKMg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>上面这张图足以说明该接口的重要性，请勿忽略它好吗？</p><p>特别提一句：的对象就是通过类创建的。<code>mybatis``SqlSessionFactory``SqlSessionFactoryBean</code></p><p>我们一起定义自己的：<code>FactoryBean</code></p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@Component</span></span>
<span class="line"><span style="color:#A6ACCD;">public class MyFactoryBean implements FactoryBean {</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    @Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    public Object getObject() throws Exception {</span></span>
<span class="line"><span style="color:#A6ACCD;">        String data1 = buildData1();</span></span>
<span class="line"><span style="color:#A6ACCD;">        String data2 = buildData2();</span></span>
<span class="line"><span style="color:#A6ACCD;">        return buildData3(data1, data2);</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    private String buildData1() {</span></span>
<span class="line"><span style="color:#A6ACCD;">        return &quot;data1&quot;;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    private String buildData2() {</span></span>
<span class="line"><span style="color:#A6ACCD;">        return &quot;data2&quot;;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    private String buildData3(String data1, String data2) {</span></span>
<span class="line"><span style="color:#A6ACCD;">        return data1 + data2;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    @Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    public Class&lt;?&gt; getObjectType() {</span></span>
<span class="line"><span style="color:#A6ACCD;">        return null;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>获取实例对象：<code>FactoryBean</code></p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@Service</span></span>
<span class="line"><span style="color:#A6ACCD;">public class MyFactoryBeanService implements BeanFactoryAware {</span></span>
<span class="line"><span style="color:#A6ACCD;">    private BeanFactory beanFactory;</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    @Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {</span></span>
<span class="line"><span style="color:#A6ACCD;">        this.beanFactory = beanFactory;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    public void test() {</span></span>
<span class="line"><span style="color:#A6ACCD;">        Object myFactoryBean = beanFactory.getBean(&quot;myFactoryBean&quot;);</span></span>
<span class="line"><span style="color:#A6ACCD;">        System.out.println(myFactoryBean);</span></span>
<span class="line"><span style="color:#A6ACCD;">        Object myFactoryBean1 = beanFactory.getBean(&quot;&amp;myFactoryBean&quot;);</span></span>
<span class="line"><span style="color:#A6ACCD;">        System.out.println(myFactoryBean1);</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><ul><li><code>getBean(&quot;myFactoryBean&quot;);</code>获取的是MyFactoryBeanService类中getObject方法返回的对象，</li><li><code>getBean(&quot;&amp;myFactoryBean&quot;);</code>获取的才是MyFactoryBean对象。</li></ul><h2 id="五-轻松自定义类型转换" tabindex="-1">五 轻松自定义类型转换 <a class="header-anchor" href="#五-轻松自定义类型转换" aria-label="Permalink to &quot;五 轻松自定义类型转换&quot;">​</a></h2><p>spring目前支持3中类型转换器：</p><ul><li>Converter&lt;S,T&gt;：将 S 类型对象转为 T 类型对象</li><li>ConverterFactory&lt;S, R&gt;：将 S 类型对象转为 R 类型及子类对象</li><li>GenericConverter：它支持多个source和目标类型的转化，同时还提供了source和目标类型的上下文，这个上下文能让你实现基于属性上的注解或信息来进行类型转换。</li></ul><p>这3种类型转换器使用的场景不一样，我们以为例。假如：接口中接收参数的实体对象中，有个字段的类型是Date，但是实际传参的是字符串类型：2021-01-03 10:20:15，要如何处理呢？<code>Converter&lt;S,T&gt;</code></p><p>第一步，定义一个实体：<code>User</code></p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Data</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">User</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Long</span><span style="color:#A6ACCD;"> id</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> name</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Date</span><span style="color:#A6ACCD;"> registerDate</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>第二步，实现接口：<code>Converter</code></p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">DateConverter</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Converter</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Date</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">SimpleDateFormat</span><span style="color:#A6ACCD;"> simpleDateFormat </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">SimpleDateFormat</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">yyyy-MM-dd HH:mm:ss</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Date</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">convert</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">source</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">source </span><span style="color:#89DDFF;">!=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">null</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&amp;&amp;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">!</span><span style="color:#89DDFF;">&quot;&quot;</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">equals</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">source</span><span style="color:#89DDFF;">))</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">try</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                simpleDateFormat</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">parse</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">source</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">catch</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">ParseException</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">e</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                e</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">printStackTrace</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">null;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>第三步，将新定义的类型转换器注入到spring容器中：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Configuration</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">WebConfig</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">extends</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">WebMvcConfigurerAdapter</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">addFormatters</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">FormatterRegistry</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">registry</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        registry</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">addConverter</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">DateConverter</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>第四步，调用接口</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">RequestMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">/user</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">RestController</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">UserController</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">RequestMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">/save</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">save</span><span style="color:#89DDFF;">(@</span><span style="color:#C792EA;">RequestBody</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">user</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">success</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>请求接口时对象中字段会被自动转换成类型。<code>User``registerDate``Date</code></p><h2 id="六-spring-mvc拦截器-用过的都说好" tabindex="-1">六 spring mvc拦截器，用过的都说好 <a class="header-anchor" href="#六-spring-mvc拦截器-用过的都说好" aria-label="Permalink to &quot;六 spring mvc拦截器，用过的都说好&quot;">​</a></h2><p>spring mvc拦截器根spring拦截器相比，它里面能够获取和 等web对象实例。<code>HttpServletRequest``HttpServletResponse</code></p><p>spring mvc拦截器的顶层接口是：，包含三个方法：<code>HandlerInterceptor</code></p><ul><li>preHandle 目标方法执行前执行</li><li>postHandle 目标方法执行后执行</li><li>afterCompletion 请求完成时执行</li></ul><p>为了方便我们一般情况会用HandlerInterceptor接口的实现类类。<code>HandlerInterceptorAdapter</code></p><p>假如有权限认证、日志、统计的场景，可以使用该拦截器。</p><p>第一步，继承类定义拦截器：<code>HandlerInterceptorAdapter</code></p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">AuthInterceptor</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">extends</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">HandlerInterceptorAdapter</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">boolean</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">preHandle</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">HttpServletRequest</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">request</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">HttpServletResponse</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">response</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">handler</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> requestUrl </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> request</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getRequestURI</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#82AAFF;">checkAuth</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">requestUrl</span><span style="color:#89DDFF;">))</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">true;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">false;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">boolean</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">checkAuth</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">requestUrl</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">===权限校验===</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">true;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>第二步，将该拦截器注册到spring容器：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Configuration</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">WebAuthConfig</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">extends</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">WebMvcConfigurerAdapter</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Bean</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">AuthInterceptor</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">getAuthInterceptor</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">AuthInterceptor</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">addInterceptors</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">InterceptorRegistry</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">registry</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        registry</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">addInterceptor</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">AuthInterceptor</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>第三步，在请求接口时spring mvc通过该拦截器，能够自动拦截该接口，并且校验权限。</p><p>该拦截器其实相对来说，比较简单，可以在类的方法中看到调用过程：<code>DispatcherServlet``doDispatch</code></p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061820097.png" alt="image-20220706182015976" style="zoom:50%;"><p>顺便说一句，这里只讲了spring mvc的拦截器，并没有讲spring的拦截器，是因为我有点小私心，后面就会知道。</p><h2 id="七-enable开关真香" tabindex="-1">七 Enable开关真香 <a class="header-anchor" href="#七-enable开关真香" aria-label="Permalink to &quot;七 Enable开关真香&quot;">​</a></h2><p>不知道你有没有用过开头的注解，比如：、、等，这类注解就像开关一样，只要在定义的配置类上加上这类注解，就能开启相关的功能。<code>Enable``EnableAsync``EnableCaching``EnableAspectJAutoProxy``@Configuration</code></p><p>是不是很酷？</p><p>让我们一起实现一个自己的开关：</p><p>第一步，定义一个LogFilter：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">LogFilter</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Filter</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">init</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">FilterConfig</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">filterConfig</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">ServletException</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">doFilter</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">ServletRequest</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">request</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">ServletResponse</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">response</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">FilterChain</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">chain</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">IOException</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">ServletException</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">记录请求日志</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        chain</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">doFilter</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">request</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> response</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">记录响应日志</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">destroy</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>第二步，注册LogFilter：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">ConditionalOnWebApplication</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">LogFilterWebConfig</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Bean</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">LogFilter</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">timeFilter</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">LogFilter</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>注意，这里用了注解，没有直接使用注解。<code>@ConditionalOnWebApplication``@Configuration</code></p><p>第三步，定义开关注解：<code>@EnableLog</code></p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Target</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">ElementType</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">TYPE</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Retention</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">RetentionPolicy</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">RUNTIME</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Documented</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Import</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">LogFilterWebConfig</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">interface</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">EnableLog</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>第四步，只需在启动类加上注解即可开启LogFilter记录请求和响应日志的功能。<code>springboot``@EnableLog</code></p><h2 id="八-resttemplate拦截器的春天" tabindex="-1">八 RestTemplate拦截器的春天 <a class="header-anchor" href="#八-resttemplate拦截器的春天" aria-label="Permalink to &quot;八 RestTemplate拦截器的春天&quot;">​</a></h2><p>我们使用调用远程接口时，有时需要在中传递信息，比如：traceId，source等，便于在查询日志时能够串联一次完整的请求链路，快速定位问题。<code>RestTemplate``header</code></p><p>这种业务场景就能通过接口实现，具体做法如下：<code>ClientHttpRequestInterceptor</code></p><p>第一步，实现接口：<code>ClientHttpRequestInterceptor</code></p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">RestTemplateInterceptor</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">ClientHttpRequestInterceptor</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">ClientHttpResponse</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">intercept</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">HttpRequest</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">request</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">byte</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">body</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">                                        </span><span style="color:#C792EA;">ClientHttpRequestExecution</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">execution</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">IOException</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        request</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getHeaders</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">set</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">traceId</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> MdcUtil</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">get</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> execution</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">execute</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">request</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> body</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>第二步，定义配置类：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Configuration</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">RestTemplateConfiguration</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Bean</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">RestTemplate</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">restTemplate</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">RestTemplate</span><span style="color:#A6ACCD;"> restTemplate </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">RestTemplate</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        restTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setInterceptors</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">Collections</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">singletonList</span><span style="color:#89DDFF;">(</span><span style="color:#82AAFF;">restTemplateInterceptor</span><span style="color:#89DDFF;">()));</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> restTemplate</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Bean</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">RestTemplateInterceptor</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">restTemplateInterceptor</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">RestTemplateInterceptor</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>其中MdcUtil其实是利用工具在中存储和获取traceId<code>MDC``ThreadLocal</code></p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">MdcUtil</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">final</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> TRACE_ID </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TRACE_ID</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">get</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> MDC</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">get</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">TRACE_ID</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">value</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        MDC</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">put</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">TRACE_ID</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> value</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>当然，这个例子中没有演示MdcUtil类的add方法具体调的地方，我们可以在filter中执行接口方法之前，生成traceId，调用MdcUtil类的add方法添加到中，然后在同一个请求的其他地方就能通过MdcUtil类的get方法获取到该traceId。<code>MDC</code></p><h2 id="九-统一异常处理" tabindex="-1">九 统一异常处理 <a class="header-anchor" href="#九-统一异常处理" aria-label="Permalink to &quot;九 统一异常处理&quot;">​</a></h2><p>以前我们在开发接口时，如果出现异常，为了给用户一个更友好的提示，例如：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">RequestMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">/test</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">RestController</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">TestController</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">GetMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">/add</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> a </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">成功</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>如果不做任何处理请求add接口结果直接报错：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061818201.png" alt="image-20220706181842120" style="zoom:67%;"><p>what？用户能直接看到错误信息？</p><p>这种交互方式给用户的体验非常差，为了解决这个问题，我们通常会在接口中捕获异常：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">GetMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">/add</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> result </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">成功</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">try</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> a </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">catch</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">e</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            result </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">数据异常</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> result</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>接口改造后，出现异常时会提示：“数据异常”，对用户来说更友好。</p><p>看起来挺不错的，但是有问题。。。</p><p>如果只是一个接口还好，但是如果项目中有成百上千个接口，都要加上异常捕获代码吗？</p><p>答案是否定的，这时全局异常处理就派上用场了：。<code>RestControllerAdvice</code></p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">RestControllerAdvice</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">GlobalExceptionHandler</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">ExceptionHandler</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">Exception</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">handleException</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">e</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">e </span><span style="color:#89DDFF;">instanceof</span><span style="color:#A6ACCD;"> ArithmeticException</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">数据异常</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">e </span><span style="color:#89DDFF;">instanceof</span><span style="color:#A6ACCD;"> Exception</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">服务器内部异常</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        retur nnull</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>只需在方法中处理异常情况，业务接口中可以放心使用，不再需要捕获异常（有人统一处理了）。真是爽歪歪。<code>handleException</code></p><h2 id="十-异步也可以这么优雅" tabindex="-1">十 异步也可以这么优雅 <a class="header-anchor" href="#十-异步也可以这么优雅" aria-label="Permalink to &quot;十 异步也可以这么优雅&quot;">​</a></h2><p>以前我们在使用异步功能时，通常情况下有三种方式：</p><ul><li>继承Thread类</li><li>实现Runable接口</li><li>使用线程池</li></ul><p>让我们一起回顾一下：</p><ol><li>继承Thread类</li></ol><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">MyThread</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">extends</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Thread</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">run</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">===call MyThread===</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">MyThread</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><ol><li>实现Runable接口</li></ol><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">MyWork</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Runnable</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">run</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">===call MyWork===</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Thread</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">MyWork</span><span style="color:#89DDFF;">()).</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><ol><li>使用线程池</li></ol><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">MyThreadPool</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">ExecutorService</span><span style="color:#A6ACCD;"> executorService </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">ThreadPoolExecutor</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">1</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">5</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">60</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> TimeUnit</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">SECONDS</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">ArrayBlockingQueue</span><span style="color:#89DDFF;">&lt;&gt;(</span><span style="color:#F78C6C;">200</span><span style="color:#89DDFF;">));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Work</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Runnable</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">run</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">===call work===</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">try</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            executorService</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">submit</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> MyThreadPool</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">Work</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">finally</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            executorService</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">shutdown</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>这三种实现异步的方法不能说不好，但是spring已经帮我们抽取了一些公共的地方，我们无需再继承类或实现接口，它都搞定了。<code>Thread``Runable</code></p><p>如何spring异步功能呢？</p><p>第一步，springboot项目启动类上加注解。<code>@EnableAsync</code></p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">EnableAsync</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">SpringBootApplication</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Application</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">SpringApplicationBuilder</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">Application</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">web</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">WebApplicationType</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">SERVLET</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">run</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">args</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>第二步，在需要使用异步的方法上加上注解：<code>@Async</code></p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">PersonService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Async</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">get</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">===add==</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">data</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>然后在使用的地方调用一下：personService.get();就拥有了异步功能，是不是很神奇。</p><p>默认情况下，spring会为我们的异步方法创建一个线程去执行，如果该方法被调用次数非常多的话，需要创建大量的线程，会导致资源浪费。</p><p>这时，我们可以定义一个线程池，异步方法将会被自动提交到线程池中执行。</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Configuration</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">ThreadPoolConfig</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Value</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">${thread.pool.corePoolSize:5}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> corePoolSize</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Value</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">${thread.pool.maxPoolSize:10}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> maxPoolSize</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Value</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">${thread.pool.queueCapacity:200}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> queueCapacity</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Value</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">${thread.pool.keepAliveSeconds:30}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> keepAliveSeconds</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Value</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">${thread.pool.threadNamePrefix:ASYNC_}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> threadNamePrefix</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Bean</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Executor</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">MessageExecutor</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">ThreadPoolTaskExecutor</span><span style="color:#A6ACCD;"> executor </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">ThreadPoolTaskExecutor</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        executor</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setCorePoolSize</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">corePoolSize</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        executor</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setMaxPoolSize</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">maxPoolSize</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        executor</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setQueueCapacity</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">queueCapacity</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        executor</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setKeepAliveSeconds</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">keepAliveSeconds</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        executor</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setThreadNamePrefix</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">threadNamePrefix</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        executor</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setRejectedExecutionHandler</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> ThreadPoolExecutor</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">CallerRunsPolicy</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#A6ACCD;">        executor</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">initialize</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> executor</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>spring异步的核心方法：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061817751.png" alt="image-20220706181753648" style="zoom:50%;"><p>根据返回值不同，处理情况也不太一样，具体分为如下情况：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061817292.png" alt="image-20220706181729211" style="zoom:67%;"><h2 id="十一-听说缓存好用-没想到这么好用" tabindex="-1">十一 听说缓存好用，没想到这么好用 <a class="header-anchor" href="#十一-听说缓存好用-没想到这么好用" aria-label="Permalink to &quot;十一 听说缓存好用，没想到这么好用&quot;">​</a></h2><p>spring cache架构图：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061814216.png" alt="image-20220706181447095" style="zoom:50%;"><p>它目前支持多种缓存：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207061815280.png" alt="image-20220706181500197" style="zoom:50%;"><p>我们在这里以为例，它是官方推荐的。<code>caffeine``spring</code></p><p>第一步，引入的相关jar包<code>caffeine</code></p><div class="language-xml"><button title="Copy Code" class="copy"></button><span class="lang">xml</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">org.springframework.boot</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">spring-boot-starter-cache</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">com.github.ben-manes.caffeine</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">caffeine</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">version</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">2.6.0</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">version</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span></code></pre></div><p>第二步，配置，开启<code>CacheManager``EnableCaching</code></p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Configuration</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">EnableCaching</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">CacheConfig</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Bean</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">CacheManager</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">cacheManager</span><span style="color:#89DDFF;">(){</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">CaffeineCacheManager</span><span style="color:#A6ACCD;"> cacheManager </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">CaffeineCacheManager</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//Caffeine配置</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">Caffeine</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">Object</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> caffeine </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> Caffeine</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">newBuilder</span><span style="color:#89DDFF;">()</span></span>
<span class="line"><span style="color:#89DDFF;">                </span><span style="color:#676E95;font-style:italic;">//最后一次写入后经过固定时间过期</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">expireAfterWrite</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">10</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> TimeUnit</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">SECONDS</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#89DDFF;">                </span><span style="color:#676E95;font-style:italic;">//缓存的最大条数</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">maximumSize</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">1000</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        cacheManager</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setCaffeine</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">caffeine</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> cacheManager</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>第三步，使用注解获取数据<code>Cacheable</code></p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Service</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">CategoryService</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span></span>
<span class="line"><span style="color:#89DDFF;">   </span><span style="color:#676E95;font-style:italic;">//category是缓存名称,#type是具体的key，可支持el表达式</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Cacheable</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">value</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">category</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">key</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">#type</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">CategoryModel</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">getCategory</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Integer</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">type</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">getCategoryByType</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">type</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">CategoryModel</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">getCategoryByType</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Integer</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">type</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">       System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">根据不同的type:</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> type </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">获取不同的分类数据</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#C792EA;">CategoryModel</span><span style="color:#A6ACCD;"> categoryModel </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">CategoryModel</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">       categoryModel</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setId</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">1L</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">       categoryModel</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setParentId</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">0L</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">       categoryModel</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setName</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">电器</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">       categoryModel</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setLevel</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">3</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> categoryModel</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>调用categoryService.getCategory()方法时，先从缓存中获取数据，如果能够获取到数据则直接返回该数据，不会进入方法体。如果不能获取到数据，则直接方法体中的代码获取到数据，然后放到缓存中。</p><h1 id="autowired-resource" tabindex="-1">@Autowired &amp; @Resource <a class="header-anchor" href="#autowired-resource" aria-label="Permalink to &quot;@Autowired &amp; @Resource&quot;">​</a></h1><p><a href="https://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&amp;mid=2247539328&amp;idx=2&amp;sn=cc10fd6fb34b4a9a470f8704ee3185d0&amp;chksm=ebd565acdca2ecbad61fa5323b6f135098bb74fecb5a36c90696516bb0bd093932b03c48361d&amp;mpshare=1&amp;scene=23&amp;srcid=05104HsHcSt3R6Pz4qEHHcCD&amp;sharer_sharetime=1683684581486&amp;sharer_shareid=29b8a04db1dbd975e3bf4e9f47e7ac67#rd" target="_blank" rel="noreferrer">相比 @Autowired，为什么更推荐你使用 @Resource ？</a></p><h2 id="区别" tabindex="-1">区别 <a class="header-anchor" href="#区别" aria-label="Permalink to &quot;区别&quot;">​</a></h2><blockquote><p><code>@Autowired</code> 和 <code>@Resource</code> 注解都是做bean注入时使用的！其中<code>@Autowired</code>时Spring提供的注解；<code>@Resource</code>并不是Spring提供的，而是JDK提供的，但是Spring支持该注解的注入，使用的时候不需要导入而外的架包。</p></blockquote><h2 id="相同点" tabindex="-1">相同点 <a class="header-anchor" href="#相同点" aria-label="Permalink to &quot;相同点&quot;">​</a></h2><blockquote><p>两者都可以写在 属性 或者 setter方法 上。如果两者都写在字段上，那么就不需要再写setter方法了！</p></blockquote><h2 id="不同点" tabindex="-1">不同点 <a class="header-anchor" href="#不同点" aria-label="Permalink to &quot;不同点&quot;">​</a></h2><h3 id="autowired" tabindex="-1">@Autowired <a class="header-anchor" href="#autowired" aria-label="Permalink to &quot;@Autowired&quot;">​</a></h3><p>默认的情况下是按照byType的方式注入！</p><p>我们可以举一个例子</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">public class TestServiceImpl() {</span></span>
<span class="line"><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">    @Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    private UserDao userDao;</span></span>
<span class="line"><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">    ... </span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>上述代码，会先去容器中查找一下，有哪些对象的类型是UserDao，找到之后把具体的值赋值到userDao中去（但是如果找到多个的话，会报错！）<code>@Autowired</code>注解是按照类型（byType）装配依赖对象的，默认情况下它要求依赖对象必须存在；如果允许null值，我们可以设置required属性为false。</p><p>如果我们想使用按照名称（byName）来装配，可以结合<code>@Qualifier</code>注解一起使用</p><blockquote><p>问：假如我们的UserDao有多个实现类，比如<code>UserDaoImpl1</code>、<code>UserDaoImpl2</code>，我们的代码要怎么写？</p></blockquote><p>如果不用 <code>@Qualifier</code> 那默认是使用byType，会找到多个UserDao类型的，会报错！！！所以要像下面的代码这样写！！！</p><p><strong>方案一（@Qualifier）</strong></p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">public class TestServiceImpl() {</span></span>
<span class="line"><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">    @Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Qualifier(&quot;userDaoImpl1&quot;)  // 指定哪一个实现类</span></span>
<span class="line"><span style="color:#A6ACCD;">    private UserDao userDao;</span></span>
<span class="line"><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">    ...   </span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p><strong>方案二（@Primary）</strong></p><p>在 <code>@Autowired</code> 中，如果有多个Bean，但是我们不想使用的@Qualifier时候，可以这样做：</p><p>使用<code>@Primary</code>注解指定一个进行注入！！！</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@Primary</span></span>
<span class="line"><span style="color:#A6ACCD;">@Mapper</span></span>
<span class="line"><span style="color:#A6ACCD;">public class UserDaoImpl01 implements UserDao {</span></span>
<span class="line"><span style="color:#A6ACCD;">    ...</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">@Mapper</span></span>
<span class="line"><span style="color:#A6ACCD;">public class UserDaoImpl02 implements UserDao {</span></span>
<span class="line"><span style="color:#A6ACCD;">    ...</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><h3 id="resource" tabindex="-1">@Resource <a class="header-anchor" href="#resource" aria-label="Permalink to &quot;@Resource&quot;">​</a></h3><p>默认按照是byName的方式注入，如果名称找不到，则按照类型注入。</p><p><code>@Resource</code> 中有两个重要的属性 name 和 type</p><p>在Spring中，将<code>@Resource</code>注解的name属性解析为bean的名称，type为bean的类型。</p><ul><li>如果使用name属性，则使用byName的自动注入策略</li><li>如果使用type属性，则使用byType的自动注入策略</li><li>如果不指定，这是将会通过反射机制使用byName自动注入策略</li></ul><h3 id="resource的装配顺序" tabindex="-1">@Resource的装配顺序 <a class="header-anchor" href="#resource的装配顺序" aria-label="Permalink to &quot;@Resource的装配顺序&quot;">​</a></h3><p>1）如果同时指定了name和type，则从Spring上下文中找到唯一匹配的bean进行装配，找不到则抛出异常</p><p>2）如果指定了name，则从上下文中查找名称(id)匹配的bean进行装配，找不到则抛出异常</p><p>3）如果指定了type，则从上下文中找到类型匹配的唯一bean进行装配，找不到或者找到多个，都会抛出异常</p><p>4）如果既没有指定name，又没有指定type，则自动按照byName方式进行装配。如果没有匹配，则回退为一个原始类型(UserDao)进行匹配，如果匹配则自动装配。（先Name后type）</p><h2 id="为什么更推荐使用-resource" tabindex="-1">为什么更推荐使用 @Resource <a class="header-anchor" href="#为什么更推荐使用-resource" aria-label="Permalink to &quot;为什么更推荐使用 @Resource&quot;">​</a></h2><blockquote><p>Resource注解在字段上，这个注解是属于J2EE的，减少了与spring的耦合。</p></blockquote><blockquote><p>但是其实啊，这个问题，我觉得很多人可能对它的理解有误！更推荐使用 <code>@Resource</code> ，我觉得不是因为<code>@Resource</code>注解性能更好之类的。而是因为其可以指定是通过 name 还是 type 的注入方式，而<code>@Autowired</code>注解本身自己是不能实现这个效果的，要和<code>@Qualifier</code>一起用才可以！</p></blockquote><h1 id="图解spring解决循环依赖" tabindex="-1">图解Spring解决循环依赖 <a class="header-anchor" href="#图解spring解决循环依赖" aria-label="Permalink to &quot;图解Spring解决循环依赖&quot;">​</a></h1><h2 id="前言-1" tabindex="-1">前言 <a class="header-anchor" href="#前言-1" aria-label="Permalink to &quot;前言&quot;">​</a></h2><p>Spring如何解决的循环依赖，是近两年流行起来的一道Java面试题。</p><p>其实笔者本人对这类框架源码题还是持一定的怀疑态度的。</p><p>如果笔者作为面试官，可能会问一些诸如“如果注入的属性为null，你会从哪几个方向去排查”这些场景题。</p><p>那么既然写了这篇文章，闲话少说，发车看看Spring是如何解决的循环依赖，以及带大家看清循环依赖的本质是什么。</p><h2 id="正文" tabindex="-1">正文 <a class="header-anchor" href="#正文" aria-label="Permalink to &quot;正文&quot;">​</a></h2><p>通常来说，如果问Spring内部如何解决循环依赖，一定是单默认的单例Bean中，属性互相引用的场景。</p><p>比如几个Bean之间的互相引用：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208111724650.png" alt="image-20220811172403516" style="zoom:50%;"><p>甚至自己“循环”依赖自己：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208111724386.png" alt="image-20220811172416292" style="zoom:50%;"><p>先说明前提：原型(Prototype)的场景是不支持循环依赖的，通常会走到<code>AbstractBeanFactory</code>类中下面的判断，抛出异常。</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#82AAFF;">isPrototypeCurrentlyInCreation</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">beanName</span><span style="color:#89DDFF;">))</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;font-style:italic;">throw</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">BeanCurrentlyInCreationException</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">beanName</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>原因很好理解，创建新的A时，发现要注入原型字段B，又创建新的B发现要注入原型字段A...</p><p>这就套娃了, 你猜是先StackOverflow还是OutOfMemory？</p><p>Spring怕你不好猜，就先抛出了BeanCurrentlyInCreationException</p><p>基于构造器的循环依赖，就更不用说了，官方文档都摊牌了，你想让构造器注入支持循环依赖，是不存在的，不如把代码改了。</p><p>那么默认单例的属性注入场景，Spring是如何支持循环依赖的？</p><h2 id="spring解决循环依赖" tabindex="-1">Spring解决循环依赖 <a class="header-anchor" href="#spring解决循环依赖" aria-label="Permalink to &quot;Spring解决循环依赖&quot;">​</a></h2><p>首先，Spring内部维护了三个Map，也就是我们通常说的三级缓存。</p><p>笔者翻阅Spring文档倒是没有找到三级缓存的概念，可能也是本土为了方便理解的词汇。</p><p>在Spring的<code>DefaultSingletonBeanRegistry</code>类中，你会赫然发现类上方挂着这三个Map：</p><ul><li><em>singletonObjects</em> 它是我们最熟悉的朋友，俗称“单例池”“容器”，缓存创建完成单例Bean的地方。</li><li><em>singletonFactories</em> 映射创建Bean的原始工厂</li><li><em>earlySingletonObjects</em> 映射Bean的早期引用，也就是说在这个Map里的Bean不是完整的，甚至还不能称之为“Bean”，只是一个Instance.</li></ul><p>后两个Map其实是“垫脚石”级别的，只是创建Bean的时候，用来借助了一下，创建完成就清掉了。</p><p>所以笔者前文对“三级缓存”这个词有些迷惑，可能是因为注释都是以Cache of开头吧。</p><p>为什么成为后两个Map为垫脚石，假设最终放在singletonObjects的Bean是你想要的一杯“<em>凉白开</em>”。</p><p>那么Spring准备了两个杯子，即<em>singletonFactories</em>和<em>earlySingletonObjects</em>来回“倒腾”几番，把热水晾成“<em>凉白开</em>”放到singletonObjects中。</p><p>闲话不说，都浓缩在图里。</p><p>上面的是一张GIF，如果你没看到可能还没加载出来。<em>三秒一帧，不是你电脑卡</em>。</p><p>笔者画了17张图简化表述了Spring的主要步骤，GIF上方即是刚才提到的三级缓存，下方展示是主要的几个方法。</p><p>当然了，这个地步你肯定要结合Spring源码来看，要不肯定看不懂。</p><p>如果你只是想大概了解，或者面试，可以先记住笔者上文提到的“三级缓存”，以及下文即将要说的本质。</p><h2 id="循环依赖的本质" tabindex="-1">循环依赖的本质 <a class="header-anchor" href="#循环依赖的本质" aria-label="Permalink to &quot;循环依赖的本质&quot;">​</a></h2><p>上文了解完Spring如何处理循环依赖之后，让我们跳出“阅读源码”的思维，假设让你实现一个有以下特点的功能，你会怎么做？</p><ul><li>将指定的一些类实例为单例</li><li>类中的字段也都实例为单例</li><li>支持循环依赖</li></ul><p>举个例子，假设有类A：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">A</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">B</span><span style="color:#A6ACCD;"> b</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>类B：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">B</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">A</span><span style="color:#A6ACCD;"> a</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>说白了让你模仿Spring：假装A和B是被@Component修饰， 并且类中的字段假装是@Autowired修饰的，处理完放到Map中。</p><p>其实非常简单，笔者写了一份粗糙的代码，可供参考：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">/**</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">     * 放置创建好的bean Map</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Map</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> cacheMap </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">HashMap</span><span style="color:#89DDFF;">&lt;&gt;(</span><span style="color:#F78C6C;">2</span><span style="color:#89DDFF;">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 假装扫描出来的对象</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">Class</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> classes </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">A</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> B</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">};</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 假装项目初始化实例化所有bean</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Class</span><span style="color:#A6ACCD;"> aClass </span><span style="color:#89DDFF;font-style:italic;">:</span><span style="color:#A6ACCD;"> classes</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#82AAFF;">getBean</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">aClass</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// check</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#82AAFF;">getBean</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">B</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">getA</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">==</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">getBean</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">A</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">));</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#82AAFF;">getBean</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">A</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">getB</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">==</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">getBean</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">B</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">class</span><span style="color:#89DDFF;">));</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">SneakyThrows</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">T</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">T</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">getBean</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Class</span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">T</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> beanClass</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 本文用类名小写 简单代替bean的命名规则</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> beanName </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> beanClass</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getSimpleName</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">toLowerCase</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 如果已经是一个bean，则直接返回</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">cacheMap</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">containsKey</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">beanName</span><span style="color:#89DDFF;">))</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">T</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> cacheMap</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">get</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">beanName</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 将对象本身实例化</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> object </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> beanClass</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getDeclaredConstructor</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">newInstance</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 放入缓存</span></span>
<span class="line"><span style="color:#A6ACCD;">        cacheMap</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">put</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">beanName</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> object</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 把所有字段当成需要注入的bean，创建并注入到当前bean中</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">Field</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> fields </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> object</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getClass</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">getDeclaredFields</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Field</span><span style="color:#A6ACCD;"> field </span><span style="color:#89DDFF;font-style:italic;">:</span><span style="color:#A6ACCD;"> fields</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            field</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setAccessible</span><span style="color:#89DDFF;">(true);</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 获取需要注入字段的class</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">Class</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">?</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> fieldClass </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> field</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getType</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> fieldBeanName </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> fieldClass</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getSimpleName</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">toLowerCase</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 如果需要注入的bean，已经在缓存Map中，那么把缓存Map中的值注入到该field即可</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 如果缓存没有 继续创建</span></span>
<span class="line"><span style="color:#A6ACCD;">            field</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">set</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">object</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> cacheMap</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">containsKey</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">fieldBeanName</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">                    </span><span style="color:#89DDFF;font-style:italic;">?</span><span style="color:#A6ACCD;"> cacheMap</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">get</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">fieldBeanName</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">getBean</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">fieldClass</span><span style="color:#89DDFF;">));</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 属性填充完成，返回</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">T</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> object</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span></code></pre></div><p>这段代码的效果，其实就是处理了循环依赖，并且处理完成后，cacheMap中放的就是完整的“Bean”了</p><p>这就是“循环依赖”的本质，而不是“Spring如何解决循环依赖”。</p><p>之所以要举这个例子，是发现一小部分盆友陷入了“阅读源码的泥潭”，而忘记了问题的本质。</p><p>为了看源码而看源码，结果一直看不懂，却忘了本质是什么。</p><p>如果真看不懂，不如先写出基础版本，逆推Spring为什么要这么实现，可能效果会更好。</p><h3 id="what-问题的本质居然是two-sum" tabindex="-1">what？问题的本质居然是two sum！ <a class="header-anchor" href="#what-问题的本质居然是two-sum" aria-label="Permalink to &quot;what？问题的本质居然是two sum！&quot;">​</a></h3><p>看完笔者刚才的代码有没有似曾相识？没错，和two sum的解题是类似的。</p><p>不知道two sum是什么梗的，笔者和你介绍一下：</p><p>two sum是刷题网站leetcode序号为1的题，也就是大多人的算法入门的第一题。</p><p>常常被人调侃，有算法面的公司，被面试官钦定了，合的来。那就来一道two sum走走过场。</p><p>问题内容是：给定一个数组，给定一个数字。返回数组中可以相加得到指定数字的两个索引。</p><p>比如：给定<code>nums = [2, 7, 11, 15], target = 9</code> 那么要返回 <code>[0, 1]</code>，因为<code>2 + 7 = 9</code></p><p>这道题的优解是，一次遍历+HashMap：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Solution</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">twoSum</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">int</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">nums</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">target</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">Map</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">Integer</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Integer</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> map </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">HashMap</span><span style="color:#89DDFF;">&lt;&gt;();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;"> nums</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">length</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">++)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> complement </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> target </span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;"> nums</span><span style="color:#89DDFF;">[</span><span style="color:#A6ACCD;">i</span><span style="color:#89DDFF;">];</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">map</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">containsKey</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">complement</span><span style="color:#89DDFF;">))</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;"> map</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">get</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">complement</span><span style="color:#89DDFF;">),</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">};</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">            map</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">put</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">nums</span><span style="color:#89DDFF;">[</span><span style="color:#A6ACCD;">i</span><span style="color:#89DDFF;">],</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">throw</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">IllegalArgumentException</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">No two sum solution</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//作者：LeetCode</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//链接：https://leetcode-cn.com/problems/two-sum/solution/liang-shu-zhi-he-by-leetcode-2/</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//来源：力扣（LeetCode）</span></span></code></pre></div><p>先去Map中找需要的数字，没有就将当前的数字保存在Map中，如果找到需要的数字，则一起返回。</p><p>和笔者上面的代码是不是一样？</p><p>先去缓存里找Bean，没有则实例化当前的Bean放到Map，如果有需要依赖当前Bean的，就能从Map取到。</p><h2 id="结尾" tabindex="-1">结尾 <a class="header-anchor" href="#结尾" aria-label="Permalink to &quot;结尾&quot;">​</a></h2><p>如果你是上文笔者提到的“陷入阅读源码的泥潭”的读者，上文应该可以帮助到你。</p><p>可能还有盆友有疑问，为什么一道“two-sum”，Spring处理的如此复杂？ 这个想想Spring支持多少功能就知道了，各种实例方式..各种注入方式..各种Bean的加载，校验..各种callback，aop处理等等..</p><p>Spring可不只有依赖注入，同样Java也不仅是Spring。如果我们陷入了某个“牛角尖”，不妨跳出来看看，可能会更佳清晰哦。</p><h1 id="eventlistener" tabindex="-1">@EventListener <a class="header-anchor" href="#eventlistener" aria-label="Permalink to &quot;@EventListener&quot;">​</a></h1><p>你好呀，我是苏三。<a href="https://mp.weixin.qq.com/s?__biz=MzkwNjMwMTgzMQ==&amp;mid=2247503600&amp;idx=2&amp;sn=ac39d119cfaeaaed3354d343002bb5fd&amp;chksm=c0e81618f79f9f0e2555885c8dffb1daaa9d4088cc8db4439a8ad3f33acb9bacd242f36e5f8b&amp;mpshare=1&amp;scene=23&amp;srcid=0424CoFbf3lcgP3gHaIAPLcW&amp;sharer_sharetime=1682296332060&amp;sharer_shareid=29b8a04db1dbd975e3bf4e9f47e7ac67#rd" target="_blank" rel="noreferrer">揭开@EventListener的神秘面纱</a></p><p>前段时间看到同事在项目里面使用了一个叫做 @EventLintener 的注解。</p><p>在这之前，我知道这个注解的用法和想要达到的目的，但是也仅限于此，其内部工作原理对我来说是一个黑盒，我完完全全不知道它怎么就实现了“监听”的效果。</p><p>现在既然已经出现在项目里面了，投入上生产上去使用了，所以我打算盘一下它，以免以后碰到问题的时候错过一个装逼的...</p><p>哦，不。</p><p>错过一个表现自己的机会。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_gif/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSPthnOF5GN2gqKYKLcXZVWs4Bz5rZRVvDHpb3dMmzB3lQ2YaRmuMrDw/640?wx_fmt=gif&amp;wxfrom=5&amp;wx_lazy=1" alt="图片"></p><h2 id="demo" tabindex="-1">Demo <a class="header-anchor" href="#demo" aria-label="Permalink to &quot;Demo&quot;">​</a></h2><p>首先，按照歪歪歪师傅的老规矩，第一步啥也别说，先搞一个 Demo 出来，没有 Demo 的源码解读，就像是吃面的时候没有大蒜，差点意思。</p><p>先铺垫一个背景吧。</p><p>假设现在的需求是用户注册成功之后给他发个短信，通知他一下。</p><p>正常来说，伪代码很简单：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">boolean success = userRegister(user);</span></span>
<span class="line"><span style="color:#A6ACCD;">if(success){</span></span>
<span class="line"><span style="color:#A6ACCD;">    sendMsg(&quot;客官，你注册成功了哦。记得来玩儿~&quot;);</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>这代码能用，完全没有任何问题。但是，你仔细想，发短信通知这个动作按理来说，不应该和用户注册的行为“耦合”在一起，难道你短信发送的时候失败了，用户就不算注册成功吗？</p><p>上面的代码就是一个耦合性很强的代码。</p><p>怎么解耦呢？</p><p>应该是在用户注册成功之后，发布一个“有用户注册成功了”的事件：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">boolean success = userRegister(user);</span></span>
<span class="line"><span style="color:#A6ACCD;">if(success){</span></span>
<span class="line"><span style="color:#A6ACCD;">    publicRegisterSuccessEvent(user);</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>然后有地方去监听这个事件，在监听事件的地方触发“短信发送”的动作。</p><p>这样的好处是后续假设不发短信了，要求发邮件，或者短信、邮件都要发送，诸如此类的需求变化，我们的用户注册流程的代码不需要进行任何变化，仅仅是在事件监听的地方搞事情就完事了。</p><p>这样就算是完成了两个动作的“解耦”。</p><p>怎么做呢？</p><p>我们可以基于 Spring 提供的 ApplicationListener 去做这个时间。</p><p>我的 Demo 里面用的 Spring 版本是 5.2.10。</p><p>这次的 Demo 也非常的简单，我们首先需要一个对象来封装事件相关的信息，比如我这里用户注册成功，肯定要关心的是 userName：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@Data</span></span>
<span class="line"><span style="color:#A6ACCD;">public class RegisterSuccessEvent {</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    private String userName;</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    public RegisterSuccessEvent(String userName) {</span></span>
<span class="line"><span style="color:#A6ACCD;">        this.userName = userName;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>我这里只是为了做 Demo，对象很简单，实际使用过程中，你需要什么字段就放进去就行。</p><p>然后需要一个事件的监听逻辑：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@Slf4j</span></span>
<span class="line"><span style="color:#A6ACCD;">@Component</span></span>
<span class="line"><span style="color:#A6ACCD;">public class RegisterEventListener {</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    @EventListener</span></span>
<span class="line"><span style="color:#A6ACCD;">    public void handleNotifyEvent(RegisterSuccessEvent event) {</span></span>
<span class="line"><span style="color:#A6ACCD;">        log.info(&quot;监听到用户注册成功事件：&quot; +</span></span>
<span class="line"><span style="color:#A6ACCD;">                &quot;{}，你注册成功了哦。记得来玩儿~&quot;, event.getUserName());</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>接着，通过 Http 接口来进行事件发布：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@Resource</span></span>
<span class="line"><span style="color:#A6ACCD;">private ApplicationContext applicationContext;</span></span>
<span class="line"><span style="color:#A6ACCD;">@GetMapping(&quot;/publishEvent&quot;)</span></span>
<span class="line"><span style="color:#A6ACCD;">public void publishEvent() {</span></span>
<span class="line"><span style="color:#A6ACCD;">    applicationContext.publishEvent(new RegisterSuccessEvent(&quot;歪歪&quot;));</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>最后把服务启动起来，调用一次：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRStsJsgib60cOAoNyWZThHHeqoAllwDLvaDDEk4lDcRXXWcbxPWBIVBqQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>输出正常，完事儿，这个 Demo 就算是搞定了，就只有十多行代码。</p><p>这么简单的 Demo 你都不想亲自动手去搭一个的话，想要靠肉眼学习的话，那么我只能说：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSHpVlqGib0wevx8TuxQRwXy8vzbJoNsvMKy5cHRtBibMS8sdib7e8kgOEw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><h2 id="debug" tabindex="-1">Debug <a class="header-anchor" href="#debug" aria-label="Permalink to &quot;Debug&quot;">​</a></h2><p>来，我问你，如果是你的话，就这几行代码，第一个断点你会打在哪里？</p><p>这没啥好犹豫的，肯定是选择打事件监听的这个地方：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSqK6xaIR0lHZF3dQyEpLhstq9bnF7XTHMdvHNFicJCic55cJZsIuiaHeLw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>然后直接就是一个发起调用，拿到调用栈再说：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSVlseuc81CW4YXVAB6pExjKCuTav3P8HJ3NIibIawdhnuqPtBJ2cN56A/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>通过观察调用栈发现，全是 Spring 的 event 包下的方法。</p><p>此时，我还是一头雾水的，完全不知道应该怎么去看，所以我只有先看第一个涉及到 Spring 源码的地方，也就是这个反射调用的地方：</p><blockquote><p>org.springframework.context.event.ApplicationListenerMethodAdapter#doInvoke</p></blockquote><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSelKwa1jvNnLftS7cssKpic93zZibrPqFQhByIsPYJTh98khbW4iceTibQQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>通过观察这三个关键的参数，我们可以断定此时确实是通过反射在调用我们 Demo 里面的 RegisterEventListener 类的 handleNotifyEvent 方法，入参是 RegisterSuccessEvent 对象，其 userName 字段的值是“歪歪”：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSnfUdIjl1TaDXallao08A0D5S61lIIbM7HPQLRhAxcNjI63eb9GXdBA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>此时，我的第一个问题就来了：Spring 是怎么知道要去触发我的这个方法的呢？</p><p>或者换个问法：handleNotifyEvent 这个我自己写的方法名称怎么就出现在这里了呢？</p><p>然后顺着这个 method 找过去一看：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSibLJkU4p58HFMh71YUricjNXiazqKu5mtcCNEP6SVwbAkACHeL9icvAKkw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>哦，原来是当前类的一个字段，随便还看到了 beanName，也是其一个字段，对应着 Demo 的 RegisterEventListener。</p><p>到这里，第二个问题就随之而来了：既然关键字段都在当前类里面了，那么这个当前类，也就是 ApplicationListenerMethodAdapter 是什么时候冒出来的呢？</p><p>带着这个问题，继续往下查看调用栈，会看到这里的这个 listener 就是我们要找的这个“当前类”：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSLMvFwox3zt08lQV6xsG3zQn33gWXDonQl4yGn9UrHT5qLrEq3icXZLg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>所以，我们的问题就变成了，这个 listener 是怎么来的？</p><p>然后你就会来到这个地方，把目光停在这个地方：</p><blockquote><p>org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent</p></blockquote><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSlibTCfD3RtvIMaUtJEUAurUsVx23N4PoX1Brp0DJXYrJ3zs6k502WjA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>为什么会在这个地方停下来呢？</p><p>因为在这个方法里面，就是整个调用链中 listener 第一次出现的地方。</p><p>所以，第二个断点的位置，我们也找到了，就是这个地方：</p><blockquote><p>org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent</p></blockquote><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSCplu8ZmInuOicaJbmZSib8hW1gje97mb3ZjVibBNbNGWxJPxlzmKibQ8NQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>但是，朋友们注意，我要但是了。</p><p>但是，当然把断点打在这个地方，重启服务准备调试的时候，你会发现重启的过程中就会停在断点处，而停下来的时候，你去调试会发现根本就不是你所关心的逻辑。</p><p>全是 Spring 启动过程中触发的一些框架的监听逻辑。比如应用启动事件，就会在断点处停下：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSkBbG9W8mZaNUDmcl6KhnO9HYmObITU10xrsPoPl9uoFOhHiaEfud0pQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>怎么办呢？</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSZ4uiaBo78UhuuicD0RDchtMNaAcSqxHp8QnB4fkMytafGMpcIibHbIyBg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>针对这种情况，有两个办法。</p><p>第一个是服务启动过程中，把断点停用，启动完成之后再次打开断点，然后触发调用。</p><p>idea 也提供了这样的功能，这个图标就是全局的断点启用和停用的图标：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSv1x082yJ0JaMe9446SPko4e1cuyFe1HpwUyIczBqs9KwtS8X06Puag/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>这个方法在我们本次调试的过程中是行之有效的，但是假设如果以后你想要调试的代码，就是要在框架启动过程中调试的代码呢？</p><p>所以，我更想教你第二种方案：使用条件断点。</p><p>通过观察入参，我们可以看到 event 对象里面有个 payload 字段，里面放的就是我们 Demo 中的 RegisterSuccessEvent 对象：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSmvaWAj1TjqACNd6FUibHz3ZxsbIEQHyIPbiaXMRJUIc8T34nFkrBhXTQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>那么，我们可不可以打上断点，然后让 idea 识别到是上述情况的时候，即有 RegisterSuccessEvent 对象的时候，才在断点处停下来呢？</p><p>当然是可以的，打条件断点就行。</p><p>在断点处右键，然后弹出框里面有个 Condition 输入框：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSrF1bk8LOHGjic4QUV2ojDqabDgGXfVeNNhySFFqbAW0eh3coq1NGGuQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>Condition，都认识吧，高考词汇，四级词汇了，抓紧时间背一背：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSxx6kWoGF9f6qDAMFXnhDT3CRbRXLxlmibA1K782jJKMKORNHFXyicjvw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>在 idea 的断点这里，它是“条件”的意思，带着个输入框，代表让你输入条件的意思。</p><p>另外，关于 Condition 还有一个短语，叫做 in good condition。</p><p>反应过来大概是“状况良好”的意思。</p><p>比如：我已出仓，in good condition。</p><p>再比如：Your hair is not in good condition。</p><p>就是说你头发状况不太好，需要注意一下。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSsUrFKSlSaicsQWcaICcgDefr9KEArRg8ff0QK8iaicye7UGDRfXxuTLtg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>扯远了，说回条件断点。</p><p>在这里，我们的条件是：event 对象里面的 payload 字段放的是我们 Demo 中的 RegisterSuccessEvent 对象时就停下来。</p><p>所以应该是这样的：</p><blockquote><p>event instanceof PayloadApplicationEvent &amp;&amp; (((PayloadApplicationEvent) event).payload instanceof RegisterSuccessEvent)</p></blockquote><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSJ9dNRadN2FLnMI3koBtvXibribKdkcibpfSia6DE5CYJ1Ppiafhy3f0Nbbw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>当我们这样设置完成之后，重启项目，你会发现重启过程非常丝滑，并没有在断点处停下来，说明我们的条件断点起作用了。</p><p>然后，我们再次发起调用，在断点处停下来了：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSKgojoIxLcMCibL8MNTyDgvFMGj6l3wjBGGZAWzf81F8TNI6fakbyCUg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>主要关注 134 行的 listener 是怎么来的。</p><p>当我们观察 getApplicationListeners 方法的时候，会发现这个方法它主要是在对 retrieverCache 这个缓存在搞事情。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRShSwkCfhmz5niaGp982UAmOfHasShTnGccJupbfZ9zWseZymJm9gv5fQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>这个缓存里面放的就是在项目启动过程中已经触发过的框架自带的 listener 对象：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSRCmjDmtbPfTibgxgBTPkickzbnTG4Zv59HDgMnoFgtN3icolHEm2yMzug/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>调用的时候，如果能从缓存中拿到对应的 listener，则直接返回。而我们 Demo 中的自定义 listener 是第一次触发，所以肯定是没有的。</p><p>因此关键逻辑就在 retrieveApplicationListeners 方法里面：</p><blockquote><p>org.springframework.context.event.AbstractApplicationEventMulticaster#retrieveApplicationListeners</p></blockquote><p>这个方法里面的逻辑较多，我不会逐行解析。</p><p>只说一下这个关键的 for 循环：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRS3nak3qicSH7ib2EGLNCJJNueLOyDyyGddBcxictkia5GZ6Cr2JP0eoIPAg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>这个 for 循环在干啥事呢？</p><p>就是循环当前所有的 listener，过滤出能处理当前这个事件的 listener。</p><p>可以看到当前一共有 20 个 listener，最后一个 listener 就是我们自定义的 registerEventListener：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSRnF3fYbiaDLMkxHCtypl892xvxNn7fxhP3DcKISksVXiaHMfx2hnVFVQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>每一个 listener 都经过一次 supportsEvent 方法判断：</p><blockquote><p>supportsEvent(listener, eventType, sourceType)</p></blockquote><p>这个方法，就是判断 listener 是否支持给定的事件：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSkJAZyEJ1cal4Uw4ZkfWOicrSlu0H5AldgdaQ5mZ91abHK0TJCFLLKDQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>因为我们知道当前的事件是我们发布的 RegisterSuccessEvent 对象。</p><p>对应到源码中，这里给定的事件，也就是 eventType 字段，对应的就是我们的 RegisterSuccessEvent 对象。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSeHOhiaD8ydWjZZIGv8YHDT2As7VPHyiaTG35zFzf1iaVJF9rLOOrDuNWA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>所以当循环到我们的 registerEventListener 的时候，在 supportsEventType 方法中，用 eventType 和 declaredEventTypes 做了一个对比，如果比上了，就说明当前的 listener 能处理这个 eventType。</p><p>前面说了 eventType 是 RegisterSuccessEvent 对象。</p><p>那么这个 declaredEventTypes 是个啥玩意呢？</p><p>declaredEventTypes 字段也在之前就出现过的 ApplicationListenerMethodAdapter 类里面。supportsEventType 方法也是这个类的方法：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSI4Z45zXBdtNPAuU0ibG2KcWS0cmMib0zJOMxEqCAyDF1kjMZRSpn5ibDA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>而这个 declaredEventTypes，就是 RegisterSuccessEvent 对象：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSSXSI4JkoWJNhTOy8jT3OARClopmsFa2Fm38OyjK0jYOrPCOJWDFH8w/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>这不就呼应上了吗？</p><p>所以，这个 for 循环结束之后，里面一定是有 registerEventListener的，因为它能处理当前的 RegisterSuccessEvent 这个事件。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSh2Pe8hwuyWM5XA3icbCKjE06AxrbpEcZecBoBUl6O7oOfIpJ9SH4Khw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>但是你会发现循环结束之后 list 里面有两个元素，突然冒出来个 DelegatingApplicationListener 是什么鬼？</p><p>这个时候怎么办？</p><p>别去研究它，它不会影响我们的程序运行，所以可以先做个简单的记录，不要分心，要抓住主要矛盾。</p><p>经过前面的一顿分析，我们现在又可以回到这里了。</p><p>通过 debug 我们知道这个时候我们拿到的就是我们自定义的 listener 了：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSUxV83ZP1lQ1cR13KY7eeC7oYNNpkiawO9VEKwAR8LVlQfHJYvjABTpA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>从这个 listener 里面能拿到类名、方法名，从 event 中能拿到请求参数。</p><p>后续反射调用的过程，条件齐全，顺理成章的就完成了事件的发布。</p><p>看到这里，你细细回想一下，整个的调试过程，是不是一环扣一环。只要思路不乱，抓住主干，问题不大。</p><h2 id="进一步思考" tabindex="-1">进一步思考 <a class="header-anchor" href="#进一步思考" aria-label="Permalink to &quot;进一步思考&quot;">​</a></h2><p>到这里，你是不是认为已经调试的差不多了？</p><p>自己已经知道了 Spring 自定义 listener 的大致工作原理了？</p><p>闭着眼睛想一想也就知道大概是一个什么流程了？</p><p>那么我问你一个问题：你回想一下我最最开始定位到反射这个地方的时候是怎么说的？</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSiatj98SFwiaiaznsMJOCXIrX4da07d489CLwL1RLSjLXpKRvSPCLiak77Q/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>是不是给了你这一张图，说 beanName、method、declaredEventTypes 啥的都在 ApplicationListenerMethodAdapter 这个类里面？</p><p>请问：这些属性是什么时候设置到这个类里面的呢？</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSL6vTXjYPsZIgMTrEsQhSia8UompKRuJnWff7SBxBr0w07U1ibz1CWTZA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>这个...</p><p>好像...</p><p>是不是确实没讲？</p><p>是的，所以说这部分我也得给你补上。</p><p>但是如果我不主动提，你是不是也想不起来呢，所以我也完全可以就写到这里就结束了。</p><p>我把这部分单独写一个小节就是提一下这个问题：如果你只是跟着网上的文章看，特别是源码解读或者方案设计类文章，只是看而不带着自己的思路，不自己亲自下手，其实很多问题你思考不全的，关键是看完以后你还会误以为你学全了。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSrJvVW6rialOE9sqSsYSiasHsFibiaYhnAicnh4Iic1W6P9BEzHsmsCUbbdag/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>现在我们看一下 ApplicationListenerMethodAdapter 这个类是咋来的。</p><p>我们不就是想看看 beanName 是啥时候和这个类扯上关系的嘛，很简单，刚刚才提到的条件断点又可以用起来了：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSNSS45hWial5x4ml7XPBg3kEk29RVTv7ibT1p5v4jt46kFa6dibT3wBWng/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>重启之后，在启动的过程中就会在构造方法中停下，于是我们又有一个调用栈了：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSmWdkfptRDroNryfGaouEN6gkibPZgqCia48XECvARVShkvDibqqlknVpQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>可以看到，在这个构造方法里面，就是在构建我们要寻找的 beanName、method、declaredEventTypes 这类字段。</p><p>而之所以会触发这个构造方法，是因为 Spring 容器在启动的过程中调用了下面这个方法：</p><blockquote><p>org.springframework.context.event.EventListenerMethodProcessor#afterSingletonsInstantiated</p></blockquote><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSr2qQtxDO7tgmIRQrU24bWlxAVKhmEa9LxrdDfzkqDV2gkTSVO62b7A/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>在这个方法里面，会去遍历 beanNames，然后在 processBean 方法里面找到带有 @EventListener 注解的 bean：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRStFC47qMDl3wboTZwebvh7cSYazMwMj4euAVO75VNOX6C7FYnJxbINg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>在标号为 ① 地方找到这个 bean 具体是哪些方法标注了 @EventListener。</p><p>在标号为 ② 的地方去触发 ApplicationListenerMethodAdapter 类的构造方法，此时就可以把 beanName，代理目标类，代理方法通过参数传递过去。</p><p>在标号为 ③ 的地方，将这个 listener 加入到 Spring 的上下文中，后续触发的时候直接从这里获取即可。</p><p>那么 afterSingletonsInstantiated 这个方法是什么时候触发的呢？</p><p>还是看调用栈：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRS4SDYicC7MeJLPm1TGSC9Mnq0OmHw0ydoxuIG6ETaibOfDUibINXaNDqiag/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>你即使再不熟悉 Spring，你至少也听说过容器启动过程中有一个 refresh 的动作吧？</p><p>就是这个地方：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSD3tUnVjo2DenNSWGBcvRjysqyoZa8LCq1O3dwUJsScicD2BOLV6yZRQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>这里，refreshContext，就是整个 SpringBoot 框架启动过程的核心方法中的一步。</p><p>就是在这个方法里面中，在服务启动的过程中，ApplicationListenerMethodAdapter 这个类和一个 beanName 为 registerEventListener 的类扯上了关系，为后续的事件发布的动作，埋好了伏笔。</p><h2 id="细节" tabindex="-1">细节 <a class="header-anchor" href="#细节" aria-label="Permalink to &quot;细节&quot;">​</a></h2><p>前面了解了关于 Spring 的事件发布机制主干代码的流程之后，相信你已经能从“容器启动时”和“请求发起时”这两个阶段进行了一个粗犷的说明了。</p><p>但是，注意，我又要“但是”了。</p><p>里面其实还有很多细节需要注意的，比如事件发布是一个串行化的过程。假设某个事件监听逻辑处理时间很长，那么势必会导致其他的事件监听出现等待的情况。</p><p>比如我搞两个事件监听逻辑，在其中一个的处理逻辑中睡眠 3s，模拟业务处理时间。发起调用之后，从日志输出时间上可以看出来，确实是串行化，确实是出现了等待的情况：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSaibGUVPzLFfyvBJJ49IJ1m2fu08zN6ibELOGANGibx2o1P9cvzIkzYkSg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>针对这个问题，我们前面讲源码关于获取到 listener 之后，其实有这样的一个逻辑：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSGKFcucUc9rJFosKN4xtPAygRKb6iaeDCicwsAZgk7NqYpcibiaeXxgqx2w/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>这不就是线程池异步的逻辑吗？</p><p>只不过默认情况下是没有开启线程池的。</p><p>开始之后，日志就变成了这样：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSBsiauZynjPicUAQ9lz0XIscrZM3MDOqibjHKicnNXmGpNLJJO1OwpEqLHA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>那么怎么开启呢？</p><p>主干流程都给你说了个大概了，这些分支细节，就自己去研究吧。</p><p>再比如，@EventListener 注解里面还有这两个参数，我们是没有使用到的：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSJ933zM6icNgia6nWe8wZxiaQm7DZZHHNoIEd8z2J5wF09wS7Jy76EQnUQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>它应该怎么使用并且其到的作用是什么呢？</p><p>对应的源码是哪个部分呢？</p><p>这也是属于分支细节的部分，自己去研究吧</p><p>再再比如，前面讲到 ApplicationListenerMethodAdapter 这个类的时候：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSqSGnCf3hLWIWaQnPibcd3gz6Y9fhhT5PXUGSYlpLa8wDrjljH3oic1rQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>你会发现它还有一个子类，点过去一看，它有一个叫做 ApplicationListenerMethodTransactionalAdapter 的儿子：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRS4wbgicvHO35SvRRCTqLRH25XianqPEwfxibWOpZcgjWMoUghaeKUvVJiaQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>这个儿子的名字里面带着个 “Transactional”，你就知道这是和事务相关的东西了。</p><p>它里面有个叫做 TransactionalEventListener 的字段，它也是一个注解，里面对应着事务的多个不同阶段：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSXiayBUrtNt17P7dicicGPrUZxaFCZt3Y6Nh3iaxlVqK3cKOuxJlKlcKcew/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>想都不用想，肯定是可以针对事务不同阶段进行事件监听。</p><p>这部分“儿子”的逻辑，是不是也可以去研究研究。</p><p>再再再比如，前面提到了 Spring 容器在启动的过程中调用了下面这个方法：</p><blockquote><p>org.springframework.context.event.EventListenerMethodProcessor#afterSingletonsInstantiated</p></blockquote><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSr2qQtxDO7tgmIRQrU24bWlxAVKhmEa9LxrdDfzkqDV2gkTSVO62b7A/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>这个方法属于哪个类？</p><p>它属于 EventListenerMethodProcessor 这个类。</p><p>那么请问这个类是什么时候出现在 Spring 容器里面的呢？</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSL6vTXjYPsZIgMTrEsQhSia8UompKRuJnWff7SBxBr0w07U1ibz1CWTZA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>这个...</p><p>好像...</p><p>是不是确实没讲？</p><p>是的，但是这个类在整个框架里面只有一次调用：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRShTEEwhO1ADsibHy8ibredYWKInrundYHl4MCRibFdgbbeZS2TMl5ukHtQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>调试起来那不是手拿把掐的事情？</p><p>也可以去研究研究嘛，看着看着，不就慢慢的从 @EventLintener 这个小口子，把源码越撕越大了？</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/ELQw2WCMgt04vBXG4Bic4R1WFTKuLhFRSg9enErIztLL385EUbcQd3MOP6IkBDD6sh9LrHc1qLnn77ibUyIPYMuA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>好了，本文的技术部分就到这里啦。</p><h1 id="spring解决循环依赖为什么需要三级缓存" tabindex="-1">Spring解决循环依赖为什么需要三级缓存？ <a class="header-anchor" href="#spring解决循环依赖为什么需要三级缓存" aria-label="Permalink to &quot;Spring解决循环依赖为什么需要三级缓存？&quot;">​</a></h1><h2 id="前言-2" tabindex="-1">前言 <a class="header-anchor" href="#前言-2" aria-label="Permalink to &quot;前言&quot;">​</a></h2><p>什么是循环依赖呢？我们抛开Spring这个框架来聊下什么是循环依赖，循环依赖可能在我们平时的开发过程中是属于比较常见的。Spring容器最大的功能就是对bean的生命周期进行管理，每个bean在创建的过程中，需要得到一个完整的bean需要对bean的所有属性进行赋值，如果两个bean出现了相互依赖的情况，如果Spring没有处理循环依赖，那么出现的结果就是在bean的创建过程中出现相互依赖，导致这个bean永远无法创建出来，则就导致一直在相互创建，那么Spring是如何来解决循环依赖的呢？</p><h2 id="什么情况下会循环依赖" tabindex="-1">什么情况下会循环依赖 <a class="header-anchor" href="#什么情况下会循环依赖" aria-label="Permalink to &quot;什么情况下会循环依赖&quot;">​</a></h2><p>1.先看如下demo: B和A相互循环依赖</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@Component</span></span>
<span class="line"><span style="color:#A6ACCD;">public class B {</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    private A a;</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">@Component</span></span>
<span class="line"><span style="color:#A6ACCD;">public class A {</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    private B b;</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>启动项目：结果没有报错。</p><ol><li>加入异步逻辑修改</li></ol><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@Component</span></span>
<span class="line"><span style="color:#A6ACCD;">public class A {</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    private B b;</span></span>
<span class="line"><span style="color:#A6ACCD;">   @Async</span></span>
<span class="line"><span style="color:#A6ACCD;">    public void test(){</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">@Component</span></span>
<span class="line"><span style="color:#A6ACCD;">public class B {</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    private A a;</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">@EnableAsync</span></span>
<span class="line"><span style="color:#A6ACCD;">public class App {</span></span>
<span class="line"><span style="color:#A6ACCD;">    public static void main(String[] args) {</span></span>
<span class="line"><span style="color:#A6ACCD;">        ApplicationContext ctx = new AnnotationConfigApplicationContext(App.class);</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>启动后：<img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZryY52M8nLNBQy13FBLMoiajndicwh0P0YOuvAj0hdTfE0se1rL3lnvILibQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>解决方案：加入lazy注解：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@Component</span></span>
<span class="line"><span style="color:#A6ACCD;">public class B {</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Lazy</span></span>
<span class="line"><span style="color:#A6ACCD;">    private A a;</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">@Component</span></span>
<span class="line"><span style="color:#A6ACCD;">public class A {</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    private B b;</span></span>
<span class="line"><span style="color:#A6ACCD;">   @Async</span></span>
<span class="line"><span style="color:#A6ACCD;">    public void test(){</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>启动后：没有异常</p><p>上面发现使用@Async异步注解，循环依赖就会报错，有可能是因为有了@Async注解修饰的方法，其对应的类被代理了，那代理了就会报错么？我们继续尝试事务注解看看。</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@Component</span></span>
<span class="line"><span style="color:#A6ACCD;">public class A {</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    private B b;</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Transactional</span></span>
<span class="line"><span style="color:#A6ACCD;">    public void test(){</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">@Component</span></span>
<span class="line"><span style="color:#A6ACCD;">public class B {</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Autowired</span></span>
<span class="line"><span style="color:#A6ACCD;">    private A a;</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">@EnableTransactionManagement</span></span>
<span class="line"><span style="color:#A6ACCD;">public class App {</span></span>
<span class="line"><span style="color:#A6ACCD;">    public static void main(String[] args) {</span></span>
<span class="line"><span style="color:#A6ACCD;">        ApplicationContext ctx = new AnnotationConfigApplicationContext(App.class);</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>启动后：正常，没有报错。</p><p>于是我们不经要问：</p><ol><li>循环依赖本来不会报错，为何添加@Async异步注解后就会导致报错</li><li>为何添加@Transactional注解就不会报错</li><li>使用了@Async异步注解的循环依赖，为何可以使用@lazy注解解决</li></ol><p>我们要想清楚上面的问题，就需要了解Bean的生命周期。</p><h2 id="bean的生命周期" tabindex="-1">Bean的生命周期 <a class="header-anchor" href="#bean的生命周期" aria-label="Permalink to &quot;Bean的生命周期&quot;">​</a></h2><p>一个简单的Bean生命周期如下：<img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZry7iak11AJ4zDM0za8YFCjZLS1pBgMeqbwDc2B8ibGuOHYHIaFSicypYlqQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>问题出现就属性赋值这里：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZrybWRuqqlOV5FPgbR3YZIwSFrBiccw5KA6ahmCWpxKgyOWntEzhAcJTxg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>由图：我们知道，当B也依赖A时，需要去容器中找到A，A已经实例化了，只是还没属性赋值，所以，不应该再实例化，解决方案：在A创建的实例化后，用一个map存起来A来不就行了么？于是有了二级缓存</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZryfQsicHqSFV045hJjHcAMw9Yu6E4VhYib432w4licnCW9TDsK3XOhfAWWQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>似乎上面已经可以解决循环依赖了，但细想一下我们会发现问题：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZryh4CrvOj9g28GE6KengTCwZ44oQcPr1ftHZG7kwlCtaCH76JXD8gasw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>通过上面的逻辑，我们发现了问题所在，B赋值属性A时，如果从Map中直接获取，那么得到的是原生对象，如果后续A没有被代理，一切没问题，如果A被代理了，那么B得到的对象就不对了，怎么解决，如果我们将aop提前是不是解决了问题。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZryK191ymoGpXVJ3DiccrmeQNbBKYzTlaxJia4vG8vrhr6mcic7XSHtZKh4g/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>由于A对象的Aop方式提前了，那么B依赖的A就是代理对象了，A对象执行赋值后，后续到Aop这一步，会判断是否已经AOP过了，是的话就不会再Aop了，问题来了:如果C也跟A相互依赖，难道C去依赖A时，也要通过ObjectFactory获取A的代理对象么？如果是这样，A就存在2个代理对象了，A是单例的，因此这样不行，于是产生了一个新的缓存，我们称之为三级缓存。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZrypb2VRxxSR2lO4WZlviaxxNSupGCID02BWWUaPmEEnLiaMpSZN5yGFAew/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>于是，spring似乎完美解决了循环依赖问题？但为何使用@Async进行异步代理，会报错？</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZryM42LJ9Qz8scuY1bE8olDhb3gUIYlU1EQKOd9mAVzvmDvSNX1XYDvGg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>我们看看报错的原因就知道：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZryyic3qWOG3mnhWNpLBnAndI7lm9KziawziaAU00mTExRZkpuZNWxAEUBqw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p><strong>那为何@Transactional修饰就没问题呢？</strong></p><p>原因是因为：ObjectFactory.getObject()方法可以产生代理对象</p><h2 id="为何使用-lazy注解修饰就能解决问题呢" tabindex="-1">为何使用@lazy注解修饰就能解决问题呢？ <a class="header-anchor" href="#为何使用-lazy注解修饰就能解决问题呢" aria-label="Permalink to &quot;为何使用@lazy注解修饰就能解决问题呢？&quot;">​</a></h2><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZryqia9dxZSEUJiangeFbqA6hKP9FHONINAhc50yqdMWRgCLqDch7IK0SzQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>我们看看源码：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZryu1sOaiaOdNQUOSvpyQt8ibXn21bHOWt1Str86zGgBEia8boIgK2Aw5o3w/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZryhGp7OxfkmuptUM7nRnr7OrFRxvHSMSOf3Y1nINWq1kR7FSrPDpSsJg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZrygDtvws4Og61xYgjOB5ibHXIjnCk48sqPxRd85d60fMcNa3cZoJKBibxA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZrySbyicsqmGTKeVuPbiatkfCibdqkDdyNnv5SwmtOQ6WdGzyBJLrQgMQtOA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZrymGxAr7Sf4nA6hzkKZw95KGcapxgP5vsZ9O8YfCc9ibsOJBaKWCl8LBQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>从源码来看，为何@Aync注解修饰，不能在ObjectFactory.getObject()方法实现代理对象：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZryZiaqhx6gfK4qHcxoslLps0yUgrpniacPLibZhOByBic3EHfibs0CFQb11Dg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZrypbjbgrqtk6WNvNLtqj8DQqCcA4OrqRgSZwDiaLPw8XUNGUK8GPaqaBQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZryY8wlsZVVhyic3bhg2LAmfyVeotBs2tqPxqu7cFR2u1IKBMia80iccTDpg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>而@Tranctional注解相关的处理器</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZryYiaPudibmTRaG3zNbSoqhG0tPRrGrjia00ekwLVgYI5GJDrd2k6qU9ebQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZrys98PdFoGXkRDeklOkGMOsvkGTydbSadr5186pnqKb2icJC8Q9U1LCMw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZryEIhP5fwPOa1Q0jDFfXTX1DLMVn71aGxbkfXn4opgzZk5Hy16NpWLyA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>那么问题？如果A已经在getObject()方法后产生了代理类，后续init（）方法后，还会执行代理么？答案是不会了，因为：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZryb1N11kDhLeXE4R2iamum0fUgicrhxaS8pTLdPtzxtz5Y3R3ia7GcAu9dw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/VbfrPx9GoVcha0bBdbLNCCq2QIGQXZryoCTl4BbtBNmrk2VElndfcDl2uOQFssKbPpbI6b5ribNiaTvmMsCJZAqg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><h2 id="总结-2" tabindex="-1">总结 <a class="header-anchor" href="#总结-2" aria-label="Permalink to &quot;总结&quot;">​</a></h2><p>本文主要以简单的案例演示了Spring的循环依赖的问题，通过梳理Bean的生命周期，让我们了解Spring为何需要三级缓存才能解决循环依赖。</p></div></div></main><footer class="VPDocFooter" data-v-6b87e69f data-v-37656e44><!--[--><!--]--><!----><nav class="prev-next" data-v-37656e44><div class="pager" data-v-37656e44><a class="pager-link prev" href="/notebook/SSM/Maven.html" data-v-37656e44><span class="desc" data-v-37656e44>Previous page</span><span class="title" data-v-37656e44>Maven</span></a></div><div class="pager" data-v-37656e44><a class="pager-link next" href="/notebook/SSM/SpringMVC.html" data-v-37656e44><span class="desc" data-v-37656e44>Next page</span><span class="title" data-v-37656e44>SpringMVC</span></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
    <script>window.__VP_HASH_MAP__=JSON.parse("{\"2、数据库_mysql_mysql面试_基础.md\":\"40da680a\",\"1、学前端_5、小程序_小程序项目.md\":\"60a1629b\",\"1、学前端_4、node_知识篇.md\":\"a7fb500e\",\"1、学前端_2、js_ts_es6 进阶.md\":\"6d07ba10\",\"1、学前端_3、vue_vue3_vue3进阶.md\":\"7ac622b4\",\"5、运维_jenkins.md\":\"929081f8\",\"1、学前端_2、js_ts_typescript.md\":\"875a4aa4\",\"2、数据库_mysql_mysql核心_设计.md\":\"7faf46d1\",\"2、数据库_mysql_mysql核心_基础.md\":\"d8e97f3e\",\"1、学前端_1、html_css_html基础.md\":\"7584d076\",\"1、学前端_5、专题篇_问题篇.md\":\"e893aaa2\",\"2、数据库_mysql_mysql面试_进阶.md\":\"f934806d\",\"3、springboot_运维_原理.md\":\"f4a39db6\",\"2、数据库_influxdb.md\":\"6e1711e1\",\"3、springboot_新特性.md\":\"cdf3e307\",\"mybatis_mybatisplus_jpa.md\":\"8e41681b\",\"1、学前端_5、小程序_小程序优化.md\":\"a2185198\",\"2、数据库_redis_redis基础.md\":\"856df0e0\",\"linux_实用脚本.md\":\"f2299dd5\",\"4、微服务_必备_分布式基础.md\":\"d49863d5\",\"2、数据库_redis_redis优化.md\":\"e66ae32f\",\"4、微服务_springsecurity_进阶篇.md\":\"235a8e9e\",\"5、运维_chatgpt.md\":\"10db3823\",\"2、数据库_mysql_分库分表.md\":\"e1c8a095\",\"start.md\":\"9bc1ff8d\",\"5、运维_github.md\":\"2ec6c735\",\"java学前端_css.md\":\"f11b47f0\",\"1、学前端_5、专题篇_知识篇.md\":\"a463ed8d\",\"linux_软件部署.md\":\"d6722925\",\"2、数据库_neo4j.md\":\"97ad22ac\",\"team.md\":\"ce467a6a\",\"nginx_实战篇.md\":\"7785486e\",\"index.md\":\"8c3ec167\",\"计算机基础_计算机网络_网络基础.md\":\"7a54a85d\",\"1、学前端_4、node_进阶篇.md\":\"60f6db69\",\"java_java集合.md\":\"a049b313\",\"1、学前端_3、vue_vue3_vue3高级.md\":\"614d1516\",\"1、学前端_5、小程序_微信小程序.md\":\"9a4be771\",\"5、运维_netty.md\":\"12ca0278\",\"2、数据库_mysql_mysql核心_运维.md\":\"83f97c16\",\"idea_vs code.md\":\"afdcb593\",\"java学前端_vue3_组件.md\":\"1086884e\",\"idea_chrome.md\":\"4a32afbc\",\"云原生_k8s.md\":\"db58e65a\",\"2、数据库_mysql_mysql核心_进阶.md\":\"61d16dff\",\"ssm_springbatch.md\":\"f799ab4a\",\"三高_分布式.md\":\"db1b8a1b\",\"2、数据库_elasticsearch_1、es基础.md\":\"04d17448\",\"linux_linux基础.md\":\"4b0bf394\",\"idea_idea插件.md\":\"fa86e45a\",\"可视化 _ 监控_可视化大屏.md\":\"004553bd\",\"2、数据库_mongodb_整合.md\":\"3c47d7f4\",\"4、微服务_springsecurity_基础篇.md\":\"534a3401\",\"4、微服务_进阶.md\":\"69095c58\",\"计算机基础_计算机基础_操作系统.md\":\"0f75d113\",\"可视化 _ 监控_zabbix.md\":\"71f2270e\",\"nginx_基础篇.md\":\"c7d8bb50\",\"1、学前端_4、node_项目实战.md\":\"bc5065b8\",\"2、数据库_redis_redis原理.md\":\"5cedf685\",\"可视化 _ 监控_监控基础.md\":\"ac56ce4d\",\"三高_高并发.md\":\"ea9ffc99\",\"2、数据库_redis_redis高级.md\":\"1d5872f6\",\"1、学前端_4、node_基础篇.md\":\"581cc13a\",\"2、数据库_mongodb_基础.md\":\"fb7a0a29\",\"idea_idea基础.md\":\"6f2f9638\",\"4、微服务_必备_sentinel.md\":\"2edfbf6c\",\"2、数据库_elasticsearch_3、es高级.md\":\"ef146606\",\"1、学前端_3、vue_vue3_vue3新语法.md\":\"8afd5409\",\"消息中间件_canal.md\":\"3949163c\",\"ssm_maven.md\":\"2c5e12ed\",\"4、微服务_springsecurity_高级篇.md\":\"882d3ff3\",\"linux_linux进阶.md\":\"188ef7b4\",\"计算机基础_设计模式_uml.md\":\"634ba256\",\"计算机基础_算法_leetcode.md\":\"77162fb9\",\"项目实战_小兔鲜_进阶篇1.md\":\"17c52c81\",\"1、学前端_2、js_ts_es6 基础.md\":\"fda3f18b\",\"项目实战_小兔鲜_进阶篇2.md\":\"a0f23006\",\"软件测试_测试基础.md\":\"8c1060cd\",\"2、数据库_redis_本地缓存.md\":\"00617fe6\",\"nginx_面试篇.md\":\"e3fb373a\",\"mybatis_mybatisplus_mybatis.md\":\"9239e0ad\",\"linux_shell.md\":\"ae53d83b\",\"2、数据库_mysql_mysql核心_优化.md\":\"36230425\",\"项目实战_项目推荐.md\":\"f9d97630\",\"mybatis_mybatisplus_mybatisplus.md\":\"0030fd35\",\"项目实战_百度地图_进阶篇.md\":\"c8b93267\",\"三高_高可用.md\":\"323840c5\",\"java_java新特性.md\":\"22abf56d\",\"软件测试_压力测试.md\":\"9ab44440\",\"java学前端_html_js.md\":\"e0fcd240\",\"2、数据库_redis_redis实战.md\":\"d6daeeab\",\"nginx_进阶篇.md\":\"e6b63195\",\"三高_秒杀.md\":\"3878bb64\",\"5、运维_git.md\":\"0264925c\",\"java_java进阶.md\":\"e79cb5b4\",\"并发 _ 多线程_基础篇.md\":\"7adbfac5\",\"项目实战_百度地图_基础篇.md\":\"8afa5954\",\"java学前端_react.md\":\"3ec827dd\",\"1、学前端_1、html_css_css基础.md\":\"01b56712\",\"项目实战_小兔鲜_基础篇.md\":\"646f5df5\",\"1、学前端_2、js_ts_js 基础.md\":\"cb13e36f\",\"可视化 _ 监控_监控进阶.md\":\"0cdbc292\",\"计算机基础_设计模式_基础篇.md\":\"51617287\",\"计算机基础_数据结构_基础篇.md\":\"b2bfd8d4\",\"项目实战_苍穹外卖_进阶篇.md\":\"48415e41\",\"ssm_spring.md\":\"ab514659\",\"消息中间件_rabbitmq.md\":\"45b1eb28\",\"1、学前端_1、html_css_网页进阶.md\":\"db998248\",\"消息中间件_kafka.md\":\"b747dabf\",\"云原生_docker.md\":\"983c7ba7\",\"4、微服务_必备_分布式锁.md\":\"5af1cf8d\",\"消息中间件_rocketmq.md\":\"d441da85\",\"项目实战_黑马头条_基础篇.md\":\"b05af3a6\",\"ssm_springmvc.md\":\"81b9714f\",\"项目实战_支付.md\":\"1d7407dd\",\"项目实战_黑马头条_进阶篇2.md\":\"bff0015b\",\"项目实战_黑马头条_进阶篇.md\":\"19f18388\",\"java学前端_vue2_组件.md\":\"58c6b1df\",\"3、springboot_基础篇.md\":\"529c66f4\",\"3、springboot_应用篇.md\":\"8b92aa61\",\"项目实战_黑马头条_高级篇.md\":\"227c08c1\",\"1、学前端_5、小程序_uniapp.md\":\"71a282b4\",\"项目实战_云尚办公_基础篇.md\":\"1fe188ba\",\"并发 _ 多线程_并发完善.md\":\"26619c46\",\"1、学前端_2、js_ts_js 进阶.md\":\"657dfb8f\",\"java_java高级.md\":\"23782d1a\",\"java_java基础.md\":\"86d67c77\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en-US\",\"dir\":\"ltr\",\"title\":\"VitePress\",\"description\":\"A VitePress site\",\"base\":\"/notebook/\",\"head\":[],\"appearance\":true,\"themeConfig\":{\"algolia\":{\"appId\":\"DW7O63I9IR\",\"apiKey\":\"f8ed758cdb288a8b06542bc35923c1a1\",\"indexName\":\"notebook\"},\"sidebar\":[{\"text\":\"Java\",\"collapsed\":true,\"items\":[{\"text\":\"Java基础\",\"link\":\"/Java/Java基础\"},{\"text\":\"Java新特性\",\"link\":\"/Java/Java新特性\"},{\"text\":\"Java进阶\",\"link\":\"/Java/Java进阶\"},{\"text\":\"Java集合\",\"link\":\"/Java/Java集合\"},{\"text\":\"Java高级\",\"link\":\"/Java/Java高级\"}]},{\"text\":\"Linux\",\"collapsed\":true,\"items\":[{\"text\":\"Linux基础\",\"link\":\"/Linux/Linux基础\"},{\"text\":\"Linux新特性\",\"link\":\"/Linux/Linux进阶\"},{\"text\":\"Shell脚本\",\"link\":\"/Linux/Shell\"},{\"text\":\"实用脚本\",\"link\":\"/Linux/实用脚本\"},{\"text\":\"软件部署\",\"link\":\"/Linux/软件部署\"}]},{\"text\":\"Nginx\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/Nginx/基础篇\"},{\"text\":\"进阶篇\",\"link\":\"/Nginx/进阶篇\"},{\"text\":\"实战篇\",\"link\":\"/Nginx/实战篇\"},{\"text\":\"面试篇\",\"link\":\"/Nginx/面试篇\"}]},{\"text\":\"SSM\",\"collapsed\":true,\"items\":[{\"text\":\"Maven\",\"link\":\"/SSM/Maven\"},{\"text\":\"Spring\",\"link\":\"/SSM/Spring\"},{\"text\":\"SpringMVC\",\"link\":\"/SSM/SpringMVC\"},{\"text\":\"SpringBatch\",\"link\":\"/SSM/SpringBatch\"}]},{\"text\":\"SpringBoot\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/3、SpringBoot/基础篇\"},{\"text\":\"应用篇\",\"link\":\"/3、SpringBoot/应用篇\"},{\"text\":\"新特性\",\"link\":\"/3、SpringBoot/新特性\"},{\"text\":\"运维&原理\",\"link\":\"/3、SpringBoot/运维&原理\"}]},{\"text\":\"SpringCloud\",\"collapsed\":true,\"items\":[{\"text\":\"SpringCloud\",\"link\":\"/4、微服务/进阶\"},{\"text\":\"Sentinel\",\"link\":\"/4、微服务/必备/Sentinel\"}]},{\"text\":\"SpringSecurity\",\"collapsed\":true,\"items\":[{\"text\":\"SpringSecurity基础篇\",\"link\":\"/4、微服务/SpringSecurity/基础篇\"},{\"text\":\"SpringSecurity进阶篇\",\"link\":\"/4、微服务/SpringSecurity/进阶篇\"},{\"text\":\"SpringSecurity高级篇\",\"link\":\"/4、微服务/SpringSecurity/高级篇\"}]},{\"text\":\"Mybatis & MybatisPlus\",\"collapsed\":true,\"items\":[{\"text\":\"Mybatis\",\"link\":\"/Mybatis&MybatisPlus/Mybatis\"},{\"text\":\"MybatisPlus\",\"link\":\"/Mybatis&MybatisPlus/MybatisPlus\"},{\"text\":\"JPA\",\"link\":\"/Mybatis&MybatisPlus/JPA\"}]},{\"text\":\"Git & ChatGPT\",\"collapsed\":true,\"items\":[{\"text\":\"Git\",\"link\":\"/5、运维/Git\"},{\"text\":\"Github\",\"link\":\"/5、运维/Github\"},{\"text\":\"ChatGPT\",\"link\":\"/5、运维/ChatGPT\"},{\"text\":\"Jenkins\",\"link\":\"/5、运维/Jenkins\"},{\"text\":\"Netty\",\"link\":\"/5、运维/Netty\"}]},{\"text\":\"数据库\",\"collapsed\":true,\"items\":[{\"text\":\"MySQL\",\"collapsed\":true,\"items\":[{\"text\":\"MySQL基础\",\"link\":\"/2、数据库/MySQL/MySQL核心/基础\"},{\"text\":\"MySQL进阶\",\"link\":\"/2、数据库/MySQL/MySQL核心/进阶\"},{\"text\":\"MySQL优化\",\"link\":\"/2、数据库/MySQL/MySQL核心/优化\"},{\"text\":\"MySQL设计\",\"link\":\"/2、数据库/MySQL/MySQL核心/设计\"},{\"text\":\"MySQL运维\",\"link\":\"/2、数据库/MySQL/MySQL核心/运维\"},{\"text\":\"分库分表\",\"link\":\"/2、数据库/MySQL/分库分表\"}]},{\"text\":\"Redis\",\"collapsed\":true,\"items\":[{\"text\":\"Redis基础\",\"link\":\"/2、数据库/Redis/Redis基础\"},{\"text\":\"Redis优化\",\"link\":\"/2、数据库/Redis/Redis优化\"},{\"text\":\"Redis原理\",\"link\":\"/2、数据库/Redis/Redis原理\"},{\"text\":\"Redis高级\",\"link\":\"/2、数据库/Redis/Redis高级\"},{\"text\":\"Redis实战\",\"link\":\"/2、数据库/Redis/Redis实战\"},{\"text\":\"本地缓存\",\"link\":\"/2、数据库/Redis/本地缓存\"}]},{\"text\":\"MongoDB\",\"collapsed\":true,\"items\":[{\"text\":\"MongoDB基础\",\"link\":\"/2、数据库/MongoDB/基础\"},{\"text\":\"MongoDB进阶\",\"link\":\"/2、数据库/MongoDB/整合\"}]},{\"text\":\"ElasticSearch\",\"collapsed\":true,\"items\":[{\"text\":\"ES基础\",\"link\":\"/2、数据库/ElasticSearch/1、ES基础\"},{\"text\":\"ES高级\",\"link\":\"/2、数据库/ElasticSearch/3、ES高级\"}]},{\"text\":\"InfluxDB\",\"link\":\"/2、数据库/influxdb\"},{\"text\":\"Neo4j\",\"link\":\"/2、数据库/Neo4j\"}]},{\"text\":\"高并发 & 秒杀 & 分布式\",\"collapsed\":true,\"items\":[{\"text\":\"分布式理论\",\"link\":\"/三高/分布式\"},{\"text\":\"分布式锁\",\"link\":\"/4、微服务/必备/分布式锁\"},{\"text\":\"秒杀\",\"link\":\"/三高/秒杀\"},{\"text\":\"高可用\",\"link\":\"/三高/高可用\"},{\"text\":\"高并发\",\"link\":\"/三高/高并发\"}]},{\"text\":\"云原生\",\"collapsed\":true,\"items\":[{\"text\":\"Docker\",\"link\":\"/云原生/Docker\"},{\"text\":\"K8S\",\"link\":\"/云原生/K8S\"}]},{\"text\":\"可视化 & 监控\",\"collapsed\":true,\"items\":[{\"text\":\"监控基础\",\"link\":\"/可视化 & 监控/监控基础\"},{\"text\":\"监控进阶\",\"link\":\"/可视化 & 监控/监控进阶\"},{\"text\":\"可视化大屏\",\"link\":\"/可视化 & 监控/可视化大屏\"},{\"text\":\"Zabbix\",\"link\":\"/可视化 & 监控/Zabbix\"}]},{\"text\":\"学前端\",\"collapsed\":true,\"items\":[{\"text\":\"HTML+CSS\",\"collapsed\":true,\"items\":[{\"text\":\"HTML基础\",\"link\":\"/1、学前端/1、HTML+CSS/HTML基础\"},{\"text\":\"CSS基础\",\"link\":\"/1、学前端/1、HTML+CSS/CSS基础\"},{\"text\":\"网页进阶\",\"link\":\"/1、学前端/1、HTML+CSS/网页进阶\"}]},{\"text\":\"JS+TS\",\"collapsed\":true,\"items\":[{\"text\":\"JS基础\",\"link\":\"/1、学前端/2、JS+TS/JS 基础\"},{\"text\":\"JS进阶\",\"link\":\"/1、学前端/2、JS+TS/JS 进阶\"},{\"text\":\"ES6基础\",\"link\":\"/1、学前端/2、JS+TS/ES6 基础\"},{\"text\":\"ES6进阶\",\"link\":\"/1、学前端/2、JS+TS/ES6 进阶\"},{\"text\":\"TS基础\",\"link\":\"/1、学前端/2、JS+TS/TypeScript\"}]},{\"text\":\"NodeJS\",\"collapsed\":true,\"items\":[{\"text\":\"Node基础\",\"link\":\"/1、学前端/4、Node/基础篇\"},{\"text\":\"Node进阶\",\"link\":\"/1、学前端/4、Node/进阶篇\"},{\"text\":\"项目实战\",\"link\":\"/1、学前端/4、Node/项目实战\"}]},{\"text\":\"Vue\",\"collapsed\":true,\"items\":[{\"text\":\"Vue3进阶\",\"link\":\"/1、学前端/3、Vue/Vue3/Vue3进阶\"},{\"text\":\"Vue3高级\",\"link\":\"/1、学前端/3、Vue/Vue3/Vue3高级\"},{\"text\":\"Vue3新语法\",\"link\":\"/1、学前端/3、Vue/Vue3/Vue3新语法\"},{\"text\":\"项目实战\",\"link\":\"/1、学前端/3、Vue/Vue2/Vue2项目\"}]},{\"text\":\"小程序\",\"collapsed\":true,\"items\":[{\"text\":\"小程序基础\",\"link\":\"/1、学前端/5、小程序/微信小程序\"},{\"text\":\"小程序优化\",\"link\":\"/1、学前端/5、小程序/小程序优化\"},{\"text\":\"uniapp\",\"link\":\"/1、学前端/5、小程序/uniapp\"},{\"text\":\"项目实战\",\"link\":\"/1、学前端/5、小程序/小程序项目\"}]}]},{\"text\":\"计算机基础\",\"collapsed\":true,\"items\":[{\"text\":\"数据结构\",\"link\":\"/计算机基础/数据结构/基础篇\"},{\"text\":\"操作系统\",\"link\":\"/计算机基础/计算机基础/操作系统\"},{\"text\":\"设计模式\",\"link\":\"/计算机基础/设计模式/基础篇\"},{\"text\":\"计算机网络\",\"link\":\"/计算机基础/计算机网络/网络基础\"},{\"text\":\"UML\",\"link\":\"/计算机基础/设计模式/UML\"},{\"text\":\"LeetCode\",\"link\":\"/计算机基础/算法/LeetCode\"}]},{\"text\":\"项目实战\",\"collapsed\":true,\"items\":[{\"text\":\"云尚办公\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/项目实战/云尚办公/基础篇\"}]},{\"text\":\"小兔鲜\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/项目实战/小兔鲜/基础篇\"},{\"text\":\"进阶篇1\",\"link\":\"/项目实战/小兔鲜/进阶篇1\"},{\"text\":\"进阶篇2\",\"link\":\"/项目实战/小兔鲜/进阶篇2\"}]},{\"text\":\"地图\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/项目实战/百度地图/基础篇\"},{\"text\":\"进阶篇\",\"link\":\"/项目实战/百度地图/进阶篇\"}]},{\"text\":\"苍穹外卖\",\"collapsed\":true,\"items\":[{\"text\":\"进阶篇\",\"link\":\"/项目实战/苍穹外卖/进阶篇\"}]},{\"text\":\"黑马头条\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/项目实战/黑马头条/基础篇\"},{\"text\":\"进阶篇\",\"link\":\"/项目实战/黑马头条/进阶篇\"},{\"text\":\"进阶篇2\",\"link\":\"/项目实战/黑马头条/进阶篇2\"},{\"text\":\"高级篇\",\"link\":\"/项目实战/黑马头条/高级篇\"}]},{\"text\":\"支付\",\"link\":\"/项目实战/支付\"},{\"text\":\"项目推荐\",\"link\":\"/项目实战/项目推荐\"}]},{\"text\":\"团队成员\",\"link\":\"/team\"}],\"siteTitle\":\"任硕的文档\",\"logo\":\"/Vue.png\",\"nav\":[{\"text\":\"Java学前端\",\"items\":[{\"items\":[{\"text\":\"HTML+JS\",\"link\":\"/Java学前端/HTML+JS\"},{\"text\":\"CSS\",\"link\":\"/Java学前端/CSS\"},{\"text\":\"Vue2+组件\",\"link\":\"/Java学前端/Vue2+组件\"},{\"text\":\"Vue3+组件\",\"link\":\"/Java学前端/Vue3+组件\"},{\"text\":\"React\",\"link\":\"/Java学前端/React\"}]}],\"activeMatch\":\"/Java/\"},{\"text\":\"软件测试\",\"items\":[{\"items\":[{\"text\":\"测试基础\",\"link\":\"/软件测试/测试基础\"},{\"text\":\"压力测试\",\"link\":\"/软件测试/压力测试\"}]}]},{\"text\":\"多线程\",\"items\":[{\"items\":[{\"text\":\"基础篇\",\"link\":\"/并发 & 多线程/基础篇\"},{\"text\":\"进阶篇\",\"link\":\"/并发 & 多线程/并发完善\"}]}]},{\"text\":\"开发工具\",\"items\":[{\"items\":[{\"text\":\"Chrome\",\"link\":\"/IDEA/Chrome\"},{\"text\":\"IDEA基础\",\"link\":\"/IDEA/IDEA基础\"},{\"text\":\"IDEA插件\",\"link\":\"/IDEA/IDEA插件\"},{\"text\":\"VS Code\",\"link\":\"/IDEA/VS Code\"}]}]},{\"text\":\"消息中间件\",\"items\":[{\"items\":[{\"text\":\"RabbitMQ\",\"link\":\"/消息中间件/RabbitMQ\"},{\"text\":\"RocketMQ\",\"link\":\"/消息中间件/RocketMQ\"},{\"text\":\"Kafka\",\"link\":\"/消息中间件/Kafka\"},{\"text\":\"Canal\",\"link\":\"/消息中间件/Canal\"}]}]}],\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/renshuo123/renshuo123.github.io\"},{\"icon\":\"twitter\",\"link\":\"#\"},{\"icon\":{\"svg\":\"<svg t=\\\"1676028692954\\\" class=\\\"icon\\\" ...</path></svg>\"},\"link\":\"https://github.com/\"}]},\"locales\":{},\"scrollOffset\":90,\"cleanUrls\":false}");</script>
    
  </body>
</html>